两个并行 Agent 同时改一份文件

多 Agent 并行执行时对同一文件并发写入,导致改动互相覆盖或产生 git 冲突。本文分析冲突根因并给出文件锁、任务分区和冲突恢复方案。

你用 Claude Code 的并行子 Agent 功能同时处理多个模块重构,完成后 git status 显示 src/utils/helpers.ts 有冲突,或者更糟——其中一个 Agent 的改动被另一个完全覆盖,没有任何冲突提示。在 AutoGen 的 GroupChat 里,两个 Coder Agent 同时收到了「更新配置文件」的任务,第二个写入的 Agent 把第一个的改动抹掉了。这类问题在 CrewAI 的 parallel tasks、LangGraph 的 fan-out 节点里都很常见,本质是多写者竞争同一资源。

常见原因

1. 任务分配没有按文件边界拆分

编排器把「重构认证模块」和「添加日志」两个任务同时分配给两个 Agent,但两个任务都需要修改 src/auth/middleware.ts。任务描述里没有明确的文件归属,两个 Agent 各自独立读取、修改、写回同一文件。

怎么判断:把两个 Agent 的任务描述列出来,检查它们是否涉及重叠的文件集合。如果有重叠,就存在冲突风险。

2. 写操作使用全量覆盖而不是增量修改

Agent 的写文件工具(如 Claude Code 的 Write 工具)会把文件全量覆盖。即使两个 Agent 修改的是不同函数,后写入的 Agent 也会用自己读取时的版本(不含第一个 Agent 的改动)覆盖文件。

怎么判断:查看两个 Agent 的 write 操作时间戳。如果写操作时间差在毫秒到秒级,且都是全量写入,就是这个问题。

3. 没有文件级锁或临界区保护

框架没有实现互斥机制,两个 Agent 可以同时持有同一文件的「写意向」。LangGraph 的 fan-out 节点默认不对文件系统加锁,CrewAI 的并行 tasks 同理。

怎么判断:检查 Agent 工具的实现代码或框架文档,确认是否有 filelockmutex 或类似机制。

4. Git worktree 没有隔离

多个 Agent 共用同一个 git 工作目录,而不是各自使用独立的 worktree 或临时分支。任何一个 Agent 的 git checkout 或文件写入都会影响其他 Agent 的视图。

怎么判断:运行 git worktree list,如果只有一个 worktree,且多个 Agent 在同时操作,就有冲突风险。

5. 任务队列没有去重

相同的文件修改任务被加入了多次(例如由于重试或消息重复投递),两个 Agent 实例分别处理了同一个任务,导致对同一文件做了两次看似独立的修改。

怎么判断:检查任务队列的消息 ID,看是否有重复的 task_id 被两个不同的 worker 处理。

最短修复路径

Step 1:按文件(或目录)边界分配任务

# 在任务分配时显式声明文件所有权
tasks = [
    {
        "agent": "agent_a",
        "description": "重构认证中间件",
        "owns_files": ["src/auth/middleware.ts", "src/auth/tokens.ts"],
        "readonly_files": ["src/types/auth.d.ts"]
    },
    {
        "agent": "agent_b",
        "description": "添加请求日志",
        "owns_files": ["src/middleware/logger.ts", "src/utils/log.ts"],
        "readonly_files": ["src/types/request.d.ts"]
    }
]

# 运行前检查 owns_files 是否有交集
def check_file_conflicts(tasks: list[dict]) -> list[str]:
    all_owned = []
    conflicts = []
    for task in tasks:
        for f in task["owns_files"]:
            if f in all_owned:
                conflicts.append(f)
            all_owned.append(f)
    return conflicts

Step 2:为每个 Agent 创建独立的 git worktree

# 为每个并行 Agent 创建独立的工作目录
git worktree add /tmp/agent-a-workdir -b agent-a-branch
git worktree add /tmp/agent-b-workdir -b agent-b-branch

# Agent 完成后合并
git checkout main
git merge agent-a-branch --no-ff -m "merge: agent-a changes"
git merge agent-b-branch --no-ff -m "merge: agent-b changes"

# 清理
git worktree remove /tmp/agent-a-workdir
git worktree remove /tmp/agent-b-workdir

Step 3:用文件锁保护写操作

import filelock

def safe_write_file(path: str, content: str, timeout: int = 30):
    """带文件锁的安全写入,防止并发覆盖。"""
    lock = filelock.FileLock(f"{path}.lock", timeout=timeout)
    try:
        with lock:
            with open(path, "w", encoding="utf-8") as f:
                f.write(content)
    except filelock.Timeout:
        raise RuntimeError(f"等待文件锁超时:{path},可能存在死锁")

Step 4:用增量 patch 代替全量覆盖

import subprocess

def apply_patch(file_path: str, diff_content: str) -> bool:
    """用 patch 命令做增量修改,而不是全量覆盖。"""
    result = subprocess.run(
        ["patch", "--forward", file_path],
        input=diff_content.encode(),
        capture_output=True
    )
    if result.returncode != 0:
        print(f"Patch 失败:{result.stderr.decode()}")
        return False
    return True

Step 5:冲突后的快速恢复

# 如果已经发生冲突,用 git 的三方合并恢复
git diff HEAD~2..HEAD~1 src/utils/helpers.ts > agent_a.patch
git diff HEAD~1..HEAD src/utils/helpers.ts > agent_b.patch

# 手动检查冲突区域
git log --oneline -5
git show HEAD~1:src/utils/helpers.ts > /tmp/before_agent_b.ts
# 对比两个版本,保留两者的改动

预防建议

  • 任务分配阶段强制声明文件所有权,运行前做冲突检测,有冲突就拒绝启动
  • 并行 Agent 默认使用独立的 git worktree,完成后由编排器统一 merge
  • 所有写文件工具改为增量 patch 模式,只修改目标函数/类,不全量覆盖
  • 在 CI 里对并行任务的输出做自动 diff,发现非预期改动时阻断合并
  • 对共享配置文件(如 tsconfig.jsonpackage.json)设置为只读,不允许 Agent 直接修改,改动必须通过人工审核
  • 任务队列使用幂等 task_id,相同 ID 的任务只处理一次,防止重复投递导致双写
  • 为并行 Agent 设置独立的临时工作目录,任务完成后再统一应用到主工作目录

常见问答 (FAQ)

Q: Claude Code 的并行子 Agent 会自动处理文件冲突吗? A: 目前不会。Claude Code 的子 Agent 共用同一个工作目录,并行写入相同文件时会直接覆盖。需要在任务设计层面确保文件边界不重叠,或者用 git worktree 隔离工作目录。

Q: LangGraph 的 fan-out 节点如何安全地并行修改文件? A: 推荐在 fan-out 节点里只做「读取 + 计算」,生成 patch 或 diff 对象而不是直接写文件,然后在 fan-in(reduce)节点里串行应用所有 patch。这样可以保证写操作有序,不存在竞态。

Q: 如果两个 Agent 修改的是同一文件的不同函数,能否自动合并? A: 理论上可以,git merge 的三方合并算法能处理同一文件不同区域的改动。但前提是两个 Agent 的改动都是基于同一个 base 版本,且改动区域没有行重叠。用 git worktree 方案可以做到这一点,合并时让 git 自动处理非冲突区域。

Q: 这个问题在只读操作时也存在吗? A: 不存在。多个 Agent 并发读取同一文件是安全的,不会产生数据竞争。只有写操作(包括 append)才需要加锁或隔离。

相关阅读

标签: #AI 编程 #Agents #排查