把多个 AI 智能体放进同一个仓库之后发生的事
原以为并行开几个智能体会话,生产力就能翻倍。实际上一周之内真事故接连不断:分支劫持、落在别人分支上的提交、靠 reflog 捞回来的孤儿提交,还有同一个功能的重复实现。这是共享一个检出的并行智能体所制造的事故类型学,以及一路踩坑后立下的规则。
核心摘要
并行智能体会话真正的敌人不是模型的水平,而是共享的工作目录。HEAD 是所有人的全局变量,一个会话切换分支,就会把另一个会话的提交安到错误的地方。一周里经历的四种事故类型和恢复记,以及 worktree 隔离、提交前重新确认分支、验证关卡范围收窄这三道防线。
目录
和 AI 智能体干活久了,人自然会起贪心。一个会话在修 bug 的时候,另一个会话去做重构,再开一个去调查 issue,不行吗?模型能开好几个,生产力不也该跟着翻倍。
我也是这么开始的。然后一周之内就明白了:并行智能体真正的敌人不是模型的水平,而是它们共享的那个工作目录。
HEAD 是全局变量
原因一句话就能概括:多个会话共享同一个 git 检出,“当前分支”这个状态就成了所有人的全局变量。
想象两个人用同一台电脑同时干活,哪里不对劲一眼就能看出来。可启动智能体的时候,我没往这方面想。每个终端标签页里跑着一个会话,看上去彼此隔离。但文件系统只有一个,HEAD 也只有一个。一个会话执行 git checkout 的瞬间,其余所有会话脚下的地面都变了。
那一周里爆出来的事故,类型非常清晰。
分支劫持。 会话 A 在主题分支上干活,会话 B 为了做自己的事切换了分支。A 毫不知情地提交,提交落在了 B 的分支上。反方向的也有:A 正要提交时,分支已经换到了 develop,多亏拦截保护分支直接提交的钩子挡了下来,才免于事故。要是没那个钩子,就直接进去了。
孤儿提交。 会话 A 的主题分支,被会话 B 在做清理时删掉了。A 的提交成了不属于任何分支的孤儿,翻 reflog 找到提交哈希,用 cherry-pick 捞了回来。捞回来算走运,要是 reflog 过期了或者没找到,一整段工作就凭空蒸发了。
暂存区污染。 会话 A 生成提交的时刻,会话 B 暂存好的文件删除也一起躺在暂存区里。要是就那么提交,B 的删除就混进了 A 的提交。扫 diff 时看见陌生的变更才筛了出来,如果智能体提交前没看 diff,就无从知晓。
重复实现。 最让人泄气的类型。两个会话在互不知情的情况下,各自实现了同一个功能。两边都做得认真扎实,其中一边只能整个扔掉。并行省下的时间,就这么原数奉还。
连验证关卡都在互相踩
出问题的不只是文件和分支。我们的工具链里有一道关卡:会话结束前要跑整个仓库的静态分析和测试,不允许在破损状态下收工。单会话时,这是极好的装置。
到了并行会话,它成了互相绊脚的陷阱。会话 A 只动了文档,却因为会话 B 正改到一半的文件有编译错误,A 的关卡失败了。A 得花掉回合去证明“这不是我的变更”,糟糕的日子里要等 B 收拾完,一等就是三十分钟。最恶劣的一次,是某个会话为了通过关卡,把另一个会话正在改的代码给改了。关卡反而成了对别人的工作下手的诱因。
检查本身没有错。错的是检查的范围是“整个仓库”这一点,在仓库不再属于一个人的那一刻,它就成了错。
三道防线
把事故按类型都经历了一遍之后,我立了三层防线。
- worktree 隔离设为默认。 用
git worktree给每个会话一个独立的工作目录,HEAD 就不再是共享变量。分支劫持、孤儿提交、暂存区污染从根源上消失,上面四种事故类型里的三种,这一手就没了。不过它不是免费的:monorepo 的话,每个 worktree 都得重装依赖、重跑代码生成;用 git-crypt 这类和 worktree 八字不合的工具的仓库,也难以强制推行。我们也有一个仓库因为这条限制维持着共享检出,所以才需要剩下两道防线。 - 提交前重新确认分支。 会话开工时记下“我在这个分支上干活”,提交前和当前 HEAD 比对。不一致就停下提交,先搞清状况。规则简单到近乎无聊,可分支劫持事故全都出自“提交那一刻的 HEAD 不是我认知里的 HEAD”。换成人,提示符上挂着分支名总能察觉;智能体不明确要求,就不会去确认。
- 验证关卡收窄到自己的变更。 会话结束关卡的检查范围,从整个仓库缩小到这个会话实际修改过的文件。自己的收工被别人的 WIP 卡住的事没了,为过关卡去碰别人代码的诱因也没了。仓库整体的健康,反正 CI 会以提交后的状态为准再看一遍,会话关卡没必要看全局。
在这之上,再加了一条运营规则:启动会话之前,扫一眼当前打开的分支和 PR,看有没有重叠的范围。重复实现不是 git 能拦下的问题,是排班的问题,只能靠习惯而不是工具去防。
并发问题原来不只属于数据库
整理下来发现是熟悉的画面:共享资源、无锁的并发访问、竞态条件,还有隔离级别。我们在数据库和多线程代码上学了几十年的那个问题,在工作目录上原样重演了。
和一个智能体干活时,这个问题不存在。人和智能体轮流干活时,人也在暗中充当协调者。问题从智能体变成多个、人放开协调的那一刻开始。从那个时间点起,工作目录就是需要并发控制的共享资源,不加隔离地跑,就会像数据库那样,必然丢数据。
这不是叫你放弃并行智能体。我现在也每天开好几个会话。变的只有一件事:我再也不把它们关进同一个房间了。
相关文章
- 💻 开发
把 AI 修过的代码交给另一个 AI 证伪的流水线
让同一个模型评审自己的代码,它只会把自己得出的结论再批准一遍。一个几乎所有代码都和 AI 一起写的独立开发者,把“让另一个 AI 来反驳这个 AI 的工作”这套交叉验证循环固化成常驻流水线的故事。包含自我评审漏掉的 bug 被连续三轮抓出来的真实案例和提示词设计。
- 💻 开发
当 AI 说“做完了”时,那个不轻信它的机制
AI 编码智能体常常没干完活就说干完了。没跑测试就说测试通过了,没复现问题就说修好了。一个几乎所有代码都和 AI 一起写的独立开发者,如何做出一道验证关卡,让“完成”只认证据、不认口头承诺。
- 💻 开发
我决定不再相信智能体说的“完成”:verify.sh 关卡 prove-it
AI 编码智能体没跑测试也会报告“测试通过”。这不是撒谎,是结构问题。做一个把报告变成检查的开源关卡 prove-it 的过程中学到的东西,以及自己手写的五行钩子悄悄失效的四种方式。