让 AI 干活不难,难的是知道它到底干对没有。 这篇记录我怎么把"两个 AI 交接班"跑成不用人管的流水线——包括第一版方案失败的全过程,那部分比成功经验值钱。
我想解决的问题很具体:一个 AI 交付成果,另一个 AI 验收成果,中间不用我传话。
最后跑通的方案核心是三样东西:
.DONE 完成信号——必须是最后写下的那一个文件配上一个轮询脚本和一层定时任务兜底,实测后台监听跑了 1 小时 05 分,被对方的完成信号自动唤醒,全程零人工介入。
最自然的想法是:让干活的 AI 自己检查一遍。这条路我一开始就放弃了。
原因是盲区。同一个模型用同一套先验看同一份产出,它看不见的地方,第二遍还是看不见。让它在自己的作业本上打勾,挑出来的基本都是显性错误——拼写、语法、格式,而真正致命的三类问题恰恰落在盲区里:
换一个模型,盲区不重叠,才有真挑出问题的可能。
所以整套方案的地基不是"两个 AI 更快",而是:两套盲区不重叠。
分工就是标题写的:Codex 负责交付,WorkBuddy 负责验收。

最初的设想是全自动:我用命令行把任务甩给 Codex,等它跑完,我接着验收。
于是试了 codex exec,参数一路往上加:
codex exec --ephemeral -s read-only --skip-git-repo-check -o out.md "任务描述"结果很干脆:200~240 秒超时,零输出。 然后逐条排查:
尝试 | 结果 |
|---|---|
stdin 接 DEVNULL | 一样超时 |
--ignore-user-config | 一样 |
改 --json,想看事件流 | 连一行事件都没吐 |
--json 连第一行事件都没有,说明它根本没进入执行阶段,卡在初始化握手上了——它需要一个常驻守护进程的管道来对话,而在我这个隔离环境里凑不出来。
⚠️ 教训(这条我给满分):客户端/IDE 里能用的 CLI,不等于能从外部脚本驱动。别跟工具链硬碰,换思路比继续加参数划算得多。
既然不能"喊",那就"留条子"。文件系统本身就是个可靠的消息队列,任何能读写文件的东西都能参与,没有任何网络依赖。
目录约定:
_handoff/
├── out/ # 交付区:干活的一方写这里
├── review/ # 验收区:验收结论写这里
└── inbox/ # 需要人工裁决的问题放这里交付方每次必须写三个文件:
文件 | 作用 |
|---|---|
<任务名>.md | 成果本身 |
<任务名>.manifest.md | 溯源清单 |
<任务名>.DONE | 完成信号(空文件) |

3.1 .DONE 是整套方案的命门
规矩只有一条,但最关键:.DONE 必须最后写。
原因特别实际:验收方是定时轮询的。如果完成信号提前落下,它会读到写了一半的成果,然后生成一份"看着像模像样、实际基于残片"的验收通过报告。
一份假的通过报告,比不验收危险得多——后者你知道没验过,前者你以为验过了。
这是我自己最满意的一个设计。交付方必须把"结论是怎么来的"写成四段:
第 2 条里那个"实测 / 推算"的区分非常狠——它会把"编了一个看起来很合理的数字"这种错误直接顶到明面上。以前我只看到结论,现在我能看到这个数是量出来的还是估出来的。
交付方放完信号,怎么让验收方知道?轮询就够了——简单、可靠、不依赖任何网络服务。
import pathlib, time
BASE = pathlib.Path(r"C:\...\_handoff")
OUT = BASE / "out"
POLL_SEC, TIMEOUT_SEC, IDLE_SEC = 5, 3600, 180 # IDLE_SEC: 多久没动静才算"停滞"
def scan():
# 完成信号:有 .DONE、且还没被处理过(改名成 .acked 即视为已处理)
done = [f for f in OUT.glob("*.DONE")
if not (OUT / (f.name + ".acked")).exists()]
# 停滞:成果文件写完超过 180 秒仍没等到 .DONE,多半是交付方卡住了
stale = [f for f in OUT.glob("*.md")
if not f.with_suffix(".DONE").exists()
and time.time() - f.stat().st_mtime > IDLE_SEC]
return done, stale
start = time.time()
while time.time() - start < TIMEOUT_SEC:
done, stale = scan()
if done:
... # 唤醒验收流程(在 WorkBuddy 侧接着做)
break
if stale:
... # 记一笔"疑似卡住",但绝不能当成完成
time.sleep(POLL_SEC)配套两个小设计,都很关键:
.DONE 改名成 .DONE.acked,同一条信号不会被处理第二次实测结果:后台监听持续 1 小时 05 分,被对方新放下的 .DONE 唤醒,全程零人工。
这一步我特意让 WorkBuddy 承担,因为它的定时任务能力正好补上了"进程可能被杀"这个短板——秒级响应靠脚本,永不漏掉靠定时任务。

第二块关键:验收不是复读对方的话。
我给自己定了"三不":

这部分我认为最值得写出来——验收方也要防自己出错:
现象 | 第一反应 | 实际情况 |
|---|---|---|
有几条记录字段明显偏短 | 像是内容被截断 | 这类记录本来就这么短,是正常形态 |
某处应该有的字段是空的 | 像是漏填了 | 内容在另一个字段里,只是叫法不同 |
某段代码疑似不在预期文件 | 像是没做 | 在别的文件里,功能正常 |
三次都差点报"缺陷"。如果当时直接下结论,就是用错误的指控消耗对方的信任——而且很快会让整套协作失去意义。
所以我给自己加了一条硬规矩:发现异常,先反证,再下结论。
如果你的场景是 "让 AI 交付,但要求每一处都有据可查"——数据处理、文档生成、代码改动、批量报告产出——这套协议可以直接套用。
起步成本很低:三个文件、一个目录、一个轮询脚本,不需要任何服务端设施,也不依赖特定模型。真正起作用的是那两条约定——完成信号最后写,以及每个数字标明实测还是推算。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。