多 Agent 协作:有副作用的动作要收归单点串行
我那条流水线上,有一段时间是这么跑的:一批任务拆开,派给若干个子代理同时写,我在旁边盯着,谁交付完了我就顺手跑一次构建看看有没有问题。听上去很自然——我是主导者,我什么都能做,代理写代理的,我查我的,各干各的互不打扰。
结果这套跑法崩了一次,而且崩得挺有意思:报错不在我动过的地方。这篇写的就是那一次,以及后来我把分工改成了什么样。我不打算在这里展开「哪些动作该收归单点」这个判据本身怎么定——那是另一件事——本篇只写这一次的经过。
当时看到的是什么
那次是并行执行的批次,几个子代理各自在写自己那份内容文件。我照旧在中途跑了一次构建,想提前看看有没有格式上的问题,别等到最后一起爆。
构建挂了。挂的位置是一个内容文件的解析失败。
最先想到的当然是「内容写错了」——某个代理产出的格式不合规,字段少了或者写歪了。这类问题在我这条线上不算稀奇,遇到的时候通常照着报错去那个文件里看一眼就能定位。
但这次去看,那个文件的内容是「一半」的。不是写错,是没写完。
是怎么发现的
这一段其实比问题本身更值得记,因为如果发现方式不对,我大概率会得出一个完全错误的结论,然后去修一个根本不存在的毛病。
当时我差点就把它归成「某个代理产出质量不行」,正准备去改派活的提示词,让它把格式写规范些。拦住我的是一个很朴素的对照动作:我把报错指向的那个文件,跟这一批的分工对了一下——那个文件属于哪个代理的活儿?
核出来的结果是,它属于一个当时还在运行、还没交付的代理。
这句话一出来,性质就变了。它写到一半,是因为它本来就还没写完;我在它写到一半的时候去构建,读到的当然是半成品。文件不是错的,是还没到能被读的时候。错的是我构建的时机。
顺着这个再往回想一步,我原本那套「先看一眼谁完成了、再跑构建」的做法本身就靠不住。查和构建是两个动作,中间隔着一段时间;只要还有代理在跑,这段时间里状态就会继续变。我查的那一刻的状况,不等于我构建的那一刻的状况。这中间的窗口,就是竞态。
我后来把这件事补进了坑记录里,原话大意就是:代理运行期禁止构建,先查后构建有竞态,是实测崩在半成品上才确认的。用「实测」两个字是有意的——我不是推理出来会有竞态,是先崩了才回头看明白。
为什么会这样
回过头看,问题出在我一开始划分工的思路上:我是按「谁有能力做这件事」来分的。
按这个思路想,构建这件事,主导者能做,子代理其实也能做;提交也是一样。既然都能做,那就谁方便谁做,我在中途顺手跑一下当然没问题。
崩了那次之后我才看清,能力根本不是关键的那一维。关键的一维是:这个动作碰不碰共享的东西。
- 每个子代理写自己那份内容文件,写的是各自独立的一块,彼此之间不重叠,谁先写谁后写都不影响别人。
- 构建不一样。它不是只看某一个文件,它要把整个内容目录当成一个整体读进去。只要目录里任何一个文件处在中间状态,整体就是不一致的。
- 版本控制的提交也一样,它记录的是仓库在某一刻的整体状态。
也就是说,写各自的文件对共享状态没有影响,构建和提交有。我原来那套分法把这两类动作混在一起看待了,才会觉得「顺手跑一下」是无害的。它不是无害的,它只是大部分时候没出事。
同一条规律在另一个场合又撞了我一次:多个会话同时在一个仓库里干活时,构建输出的那个目录是共用的,两边会互相覆盖,看到的结果就都不可信了。处置很直接——各自用各自的输出目录,别共用。本质还是同一件事:共用的那个东西一旦有两个写入方,谁都别想拿到干净的结果。
改成了什么
我把执行模型明确写死成了两条,落进了那条产线的文档里:
第一条,权限上的划分:子代理只被允许写内容文件;构建和版本控制由主导者独占串行执行。
这条改的是「谁能碰什么」。代理拿到的任务描述里,动作范围就只有「写你那份文件」,构建、提交这些动作不在它的活儿范围内,也不允许它顺手做。我这边收口,一次只有我一个人在做这些事。
第二条,时机上的划分:代理还在运行期间,不构建。
这条改的是「什么时候能做」。它是第一条的补丁——光有第一条其实还堵不住,因为我自己就是那个独占构建的人,我在代理跑的中途去构建,串行的前提已经不成立了。所以这条是给我自己定的:等这一批全都交付完,再动构建。
两条合起来才是完整的,缺任何一条那次崩溃都还会再来。第一条管住了别人,第二条管住了我自己,而实际把我坑到的恰恰是第二条那种情况。
改完之后还有个附带的好处:中途构建这个动作一取消,我盯批次的节奏反而清楚了。以前是边跑边看边构建,脑子里同时挂着好几件事;现在是等、收、然后统一走一遍构建与提交。事情少了,但每一步的结果是可信的。
从这一次里带走的那句话
如果只留一句,我会留这个:
判断哪些动作能并行、哪些必须收归单点,不要看「谁有能力做」,要看「这个动作对共享状态有没有副作用」。有副作用的动作收归单点串行。
我在派活的时候习惯性地会想「这个代理能不能干这个」,这是从「人能不能胜任」那套思路里带过来的惯性。但并发安全跟能力没关系。一个完全有能力构建的代理,在别人还在写的时候构建,得到的结果照样是错的;而一个只被允许写自己那份文件的代理,哪怕能力再有限,也踩不到别人。
还有半句我也想记下来:发现方式往往比结论更值钱。 那次如果我没去把报错文件跟这一批的分工对一遍,我会得出「代理产出格式不规范」这个结论,然后去改提示词。提示词改一百遍,中途构建照样会崩,因为问题压根不在那儿。把现象跟「这是谁的活儿、他现在跑完没有」对上,才是真正把问题揪出来的那一下。
这篇写的是我自己那套流程里的一次实际情况,不是通行做法。你的场景、工具和团队规模不同,结论未必适用,判断方式可能比结论更值得拿走。
延伸阅读
- 上一篇(协作与移交):评审 Agent 必须有权质疑事实卡本身
- 下一篇(协作与移交):多个 Agent 会话并发在同一个仓库会互相踩踏
- 这个专题的主论点:Agent 调用没报错,不等于这件事办成了
- 专题导读与七个阶段的地图:把 Agent 当成一个要上岗的员工来带:这个专题讲什么