双代理隔离上下文,分工测试与实现
文章提出让Codex与Claude Code在独立工作树和上下文中分别编写测试、完成实现,仅共享规格。目的是减少测试迎合实现的偏差,避免同一代理生成彼此自洽却偏离需求的代码与测试。
AI 深度解读
让同一个 AI 同时写函数和测试,容易得到一套彼此认同的答案:代码这样实现,测试也按这个思路验证。至于两者是否符合需求,测试未必能回答。作者给出的办法是让 Codex 先按规格写测试,再让 Claude Code 在另一个 Git worktree 中完成实现。
这套流程值得看的地方,是它把「别改测试」变成了可检查的工程约束。但结论也有限:它能证明测试先于实现、实现过程中测试没有变化,不能证明测试正确,更没有证据说明这样交付的 bug 更少。
两个 Agent 分工,为什么还要两个 worktree
作者用一个 TypeScript 函数演示整个过程:src/duration.ts 导出 parseDuration(input: string): number,把时长字符串转换成整数秒。
规格写在 SPEC.md 中:单位只有 h、m、s,至少出现一段,顺序固定,每个单位最多出现一次。"1h30m" 返回 5400,"45s" 返回 45,"2h" 返回 7200;首尾空白可以忽略,所以 " 10m " 返回 600。""、"90"、"1x"、"30m1h"、"1h1h"、"-5m" 都必须抛出 RangeError。
如果两个角色共享上下文,测试作者的分析仍可能影响实现者。作者因此把交接点放到 Git:Codex 在自己的分支和工作目录里写测试,提交后,再把这个提交交给 Claude Code。
worktree 可以理解成同一仓库的另一份独立工作目录,各自切换分支、修改文件,互不挤占现场。示例通过 git worktree add -b tests/duration ../duration-tests main 和 git worktree add -b impl/duration ../duration-impl main 建立两个目录。
需要隔离的是解题过程,而规格必须共享。
开始前,main 上只有规格、函数占位实现、测试脚本和权限配置。占位函数直接抛出 Error,尚不存在可供测试作者照着写的业务逻辑。
测试使用 node:test 和 node:assert/strict,脚本是 node --test "test/**/*.test.ts"。原文指出,node:test 从 Node 20 起稳定,类型剥离从 Node 22.18.0 和 23.6.0 起默认开启,因此这个小例子不需要安装测试框架或 TypeScript 执行器。不过,类型剥离只负责擦除类型,导入路径需要带 .ts,示例也不能使用需要运行时转换的 enum。
测试交给实现者之前,人要审哪一步
进入测试目录后,作者用 codex --sandbox workspace-write --ask-for-approval on-request 启动 Codex,要求它只读规格,在 test/duration.test.ts 中编写测试,并从 ../src/duration.ts 导入函数。
任务中最关键的限制是:每个预期结果必须来自 SPEC.md,只能修改 test/,并运行 npm test,确认测试因占位实现而失败。
这里要防的是循环验证。例如,把测试里的期望值也用一套解析逻辑计算出来,两边可能一起算错。直接断言 "1h30m" 等于 5400,才能把规格中的确定答案固定下来。
作者没有为 Codex 设置额外的路径权限规则,而是在提交前人工检查:git status --short 应该只出现 test/ 下的文件,npm test 应该失败,人还要逐条阅读断言,确认它们对应规格。随后执行 git add test,再用 git commit -m "Tests for parseDuration, from SPEC.md" 固化测试。
「先红」也要看失败原因。导入路径错误同样会让测试失败,却没有验证业务行为。这个例子中的非法输入测试,还必须检查 RangeError 类型;若只断言「抛出了异常」,占位函数就可能蒙混过关。
然后进入实现目录,执行 git cherry-pick tests/duration,再跑一次 npm test,把同一份失败测试交给 Claude Code。启动命令是 claude --permission-mode acceptEdits。
给实现者的指令也留了一个必要出口:只修改 src/;如果认为测试有错,停止并说明具体是哪条、为什么有错。冻结测试不该变成强迫实现者服从错误断言。
权限规则能拦修改,Git 才负责验收
作者预先在 .claude/settings.json 中放入以下配置,保护测试、测试入口和权限规则自身:
{
"permissions": {
"deny": [
"Edit(/test/**)",
"Edit(/package.json)",
"Edit(/.claude/**)"
]
}
}
要留意后两行:只保护测试文件还不够,修改 package.json 可以改变执行哪些测试,修改 .claude/ 则可能改变约束本身。按原文引用的权限说明,开头的 / 锚定会话的主工作目录,在这里就是对应的 worktree。
acceptEdits 会自动批准文件编辑,以及工作目录中的 mkdir、touch、rm、rmdir、mv、cp、sed 等操作;其他 Bash 命令除内置只读集合外仍可能询问。原文指出,deny 规则在所有模式下生效,包括 bypassPermissions,但覆盖范围存在边界。
它能约束内置文件工具、Claude Code 能识别的部分 Bash 文件操作和重定向目标,却无法覆盖所有间接读写。例如,Python 或 Node 子进程自己打开文件,不能仅靠这些规则阻止。操作系统层面的隔离需要 sandboxing。
所以,测试全绿后还要执行这组三项检查:
git diff --exit-code tests/duration -- \
test/ package.json .claude/
git status --short -- test/
git diff --stat tests/duration
第一条发现受保护路径有差异时返回非零状态;第二条尤其重要,它补上了 git diff 看不到的未跟踪文件;第三条检查整体改动范围,预期只涉及 src/。
权限规则降低改动测试的机会,最终差异检查提供验收依据。
验收后才提交实现并合入 main。清理时还有一个 Git 细节:git cherry-pick 会生成新的提交,原测试提交并未直接进入主线,因此 git branch -d tests/duration 可能拒绝删除。作者在检查通过后使用 git branch -D tests/duration。
作者开发的 Hermes IDE 把这些操作包装成会话和审查界面。原文展示的是 Hermes 2.3.4,每个 Agent 使用独立的 hermes/ 分支和 worktree,Review Desk 可以按轮次查看修改。但作者明确承认自己的产品立场,也强调终端可以完成全部流程,IDE 仍不能代替最后的 Git 检查。

我的判断
这套方案最有价值的部分,是让验收标准提前固定,并留下可追溯的交接记录。相比只在提示词中写「不许修改测试」,它多了提交、权限和差异检查几个具体约束,审查者也更容易确认过程是否符合约定。
不过,两个 Agent 不会自动带来两份正确理解。规格有歧义,或者 Codex 理解错了,Claude Code 仍可能认真实现错误要求。测试提交前的人审因此不能省。
它也不能阻止对样例过拟合。只测试 "1h30m",实现者完全可能针对这个输入返回 5400。补充由规格推导的组合用例、检查实际实现,比只看测试是否全绿更有意义。git diff --stat 能提示改动规模,是否存在硬编码仍要读代码。
同样,原文没有提供 Codex 搭配 Claude Code、同一种模型分开会话、单 Agent 工作流之间的对照数据,作者也明确说没有缺陷率下降的证据。应把它看成一种可审计的工作流程,不能直接当作模型组合更可靠的证明。面对需求尚未稳定、测试设施又复杂的项目,维护两套工作目录和提交交接也有成本。
能拿来做什么
- 给边界清楚的 TypeScript 工具函数写规格时,把合法示例、异常类型、空白处理和顺序限制列全,再让测试 Agent 独立生成断言。规格越含糊,分工越难产生价值。
- 在日常 AI 编程中,先人工审查并提交测试,再启动新的实现会话。即使暂时不用两个模型,也可以保留「先固定验收条件,再交给实现者」的交接方式。
- 采用原文的 Claude Code 配置时,同时保护
test/、package.json和.claude/,并把三条 Git 检查纳入验收,尤其检查新增但未跟踪的测试文件。 - 为实现 Agent 写明争议处理方式:发现测试与规格冲突时,报告具体断言和依据,由人确认后重新提交测试,再继续实现。