AIGC标识 多个 Agent 同时修改一个文件怎么办?从 Pi 源码到 Codex 的并发处理

先给结论:不要让多个 Agent 直接抢写同一个工作区

多个 Agent 同时修改一个文件,真正危险的不是“两个补丁都失败”,而是后写入的 Agent 没意识到文件已经变化,直接把前一个 Agent 的修改覆盖掉。可靠的处理方式应该分成三层:先划分写入边界,再在写入时检查文件状态,最后用测试和 diff 验证合并结果。

如果任务可以拆成不同文件,优先并行;如果必须修改同一个文件,就把它当成一个需要串行调度的资源。并行的是分析和测试,不是对同一个文件的最终写入。

一、Pi 是怎么处理同一个文件并发写入的

Pi 的代码仓库是 badlogic/pi-mono,相关实现位于 packages/coding-agent/src/core/tools。它没有把“多个 Agent 修改同一个文件”交给模型自己运气解决,而是在文件变更工具外面加了一层按文件串行化的队列。

1. edit 和 write 都经过同一个队列

在 packages/coding-agent/src/core/tools/edit.ts 中,编辑工具先把相对路径解析为绝对路径,然后调用:

return withFileMutationQueue(absolutePath, async () => {
  // access -> read -> match -> write
});

write.ts 也使用同一个 withFileMutationQueue。这点很关键:不是只有“精确替换”被保护,完整覆盖写入也被纳入同一套调度。

2. 队列按真实路径归一化

Pi 的 file-mutation-queue.ts 维护一个 Map<string, Promise<void>>。它先对路径执行 resolve,然后尽量调用 realpath,最后用这个规范化后的路径作为队列 key。这样,同一个文件的不同相对路径或符号链接别名,也不会意外绕过队列。

const fileMutationQueues = new Map<string, Promise<void>>();

const currentQueue = fileMutationQueues.get(key) ?? Promise.resolve();
const chainedQueue = currentQueue.then(() => nextQueue);
fileMutationQueues.set(key, chainedQueue);

await currentQueue;
try {
  return await fn();
} finally {
  releaseNext();
}

3. 同一个文件串行,不同文件仍然并行

Pi 自带测试 packages/coding-agent/test/file-mutation-queue.test.ts 明确验证了两件事:同一个文件的两个操作按先后顺序执行;不同文件的操作可以并行执行。它还测试了符号链接别名会进入同一个队列。

这是一种很实用的粒度:锁太大,会让所有 Agent 互相等待;锁太小,又挡不住同一个 inode 上的覆盖写入。按规范化文件路径排队,通常比按“任务名称”或“目录说明”做粗粒度声明更可靠。

4. Pi 的 edit 还会拒绝模糊替换

Pi 的 edit 工具要求 oldText 在原始文件中唯一匹配,并且多个替换不能重叠。它会先读取文件,再匹配和生成新内容,最后写回;如果匹配不到或匹配不唯一,就失败,而不是猜测应该改哪一处。

因此,Pi 的策略可以概括为:同一文件排队,写入前重新读取,精确匹配失败就停下来。不过要注意,这个队列主要解决同一 Pi 运行时内的并发。如果两个完全独立的进程共享同一个工作区,仍然需要 Git worktree、跨进程锁或更高层的协调器。

二、Codex 的 apply_patch 怎么处理文件变化

Codex CLI 的源码位于 openai/codex 仓库。它的文件修改入口是 apply_patch,实现位于 codex-rs/apply-patch。它使用带上下文的文件补丁,而不是简单地把模型生成的完整文件内容无条件覆盖上去。

1. 一个 patch 可以描述多个文件操作

Codex 的补丁格式包含 *** Begin Patch 和 *** End Patch,中间用 *** Add File、*** Update File、*** Delete File 描述文件动作。更新文件时,使用 @@ hunk 和上下文行定位修改位置。

*** Begin Patch
*** Update File: src/service.ts
@@
-const timeout = 1000;
+const timeout = 2000;
*** End Patch

2. Codex 会重新读取当前文件并匹配上下文

源码中的 derive_new_contents_from_chunks 会先从文件系统读取当前内容,然后调用替换计算逻辑。seek_sequence 会尝试用逐渐宽松的规则寻找补丁上下文:先精确匹配,再忽略行尾空白,最后忽略首尾空白。找不到预期上下文时,Codex 返回错误,而不是静默覆盖当前文件。

这意味着:如果 Agent 读完文件后,另一个 Agent 修改了同一段代码,原补丁的上下文很可能不再匹配,Codex 会暴露出状态漂移。正确恢复动作是重新读取文件、查看当前 diff、重新计算 patch,而不是强行重试旧 patch。

3. Codex 的并行 Agent 更适合放在隔离 worktree

Codex 官方文档对 subagent 的建议很明确:并行 Agent 更适合代码探索、测试、排查和总结;多个 Agent 同时写代码会增加冲突和协调成本。对于需要并行修改的任务,应让每个写入型任务使用独立的 Git worktree,最后由主 Agent 按顺序合并和验证。

worktree 的价值是把“两个 Agent 同时写同一个工作区”变成“两个分支分别产生变更”。每个 Agent 可以自由修改自己的文件副本,主 Agent 再通过 commit、diff、rebase 或 merge 处理结果。即使最终仍有语义冲突,也不会在共享工作区里发生无提示覆盖。

三、实际工作流:我会怎样处理多个 Agent 的修改

步骤 1:先做写集分析

让 Agent 先只读分析,列出预计修改的文件和关键符号。把任务分成三类:

  • 只读任务:源码阅读、依赖追踪、测试分析,可以并行。
  • 互不重叠的写任务:不同文件或明确分开的目录,可以并行。
  • 重叠写任务:同一个文件、同一个函数或同一段配置,必须串行或放入独立 worktree。

步骤 2:给共享文件指定唯一写入者

最简单有效的规则是“一个文件同一时间只有一个 owner”。其他 Agent 可以提出 patch、写分析或准备测试,但不要直接写共享文件。owner 完成后提交一个小 commit,再把最新文件状态交给下一个 Agent。

步骤 3:写入前检查状态

git status --short
git diff -- path/to/file
git log -5 --oneline -- path/to/file

如果当前文件已经和 Agent 上次读取的不一样,就停止使用旧上下文,重新读取文件再生成 patch。对于重要文件,可以记录读取时的 hash;写入前重新计算 hash,变化就阻止写入。

步骤 4:优先用小 patch,避免整文件覆盖

小 patch 的上下文更容易审查,也更容易在冲突后重新生成。不要让两个 Agent 都用“读取旧文件后生成完整新文件”的方式写回,因为这种方式最容易丢失对方修改。

步骤 5:合并后立即验证

git diff --check
npm test -- --runInBand
# 或运行项目对应的最小测试集

验证不只是看 patch 是否成功应用,还要检查接口调用方、类型检查、格式化结果和关键行为测试。补丁应用成功,只能说明文本替换完成,不能说明两个 Agent 的意图已经正确合并。

四、几种常见错误处理

  • Invalid Context:文件已经漂移。重新读取当前文件,基于最新内容生成 patch。
  • 同一文件被覆盖:回到 Git diff 和提交记录,确认哪个变更丢失,再从两个独立版本手工合并。
  • 多个 Agent 都改了配置入口:把配置文件指定为单一 owner,其余 Agent 只提交需求或测试。
  • 并行任务互相依赖:先完成接口或数据结构变更,再让下游 Agent 读取新状态修改调用方。
  • 共享工作区无法避免:至少使用跨进程文件锁、写入前 hash 检查和提交前 diff 审查;仅靠 Agent 的自然语言约定不够。

结语

Pi 给出的启发是把文件写入当成资源调度问题:同一文件串行,不同文件并行,并且精确匹配失败就拒绝写入。Codex 给出的启发是把补丁当成带上下文的变更:先读取当前文件,匹配失败就暴露冲突;需要真正并行时,用 worktree 隔离,再由主 Agent 合并。

所以,多个 Agent 同时修改一个文件时,最稳妥的答案不是“让它们自己协调”,而是建立明确的写入边界:读可以并行,写要按文件串行;能隔离就用 worktree,不能隔离就做写前状态检查;每次合并后都要验证。

参考源码:
Pi:packages/coding-agent/src/core/tools/edit.ts、write.ts、file-mutation-queue.ts。
Codex:codex-rs/apply-patch/src/lib.rs、file_update.rs、seek_sequence.rs。

posted @ 2026-10-24 09:05  fitch_liu  阅读(15)  评论(0)    收藏  举报