文章

写入前预留文件:解决多 AI 代理在同一仓库的代码冲突

三个代理在同一个检出目录中无规则运行会在一分钟内发生覆盖。为每个代理分配独立 worktree 会切断彼此代码的可见性。行之有效的方案是由服务器强力仲裁的文件路径租约,同一时刻只允许单一持有者,其余代理全部被拦截。

让三个代理在同一个仓库中无约束运行,它们在不到一分钟内就会互相覆盖。最后写入者悄无声息地覆盖前者,直到测试失败才暴露问题。显而易见的解决办法是各给一份分支检出,但对于协作开发而言,这种做法完全适得其反。

Two agents reach for the same file. One reservation is granted; the other is refused at the board.the boardclaude-1codex-2src/types/order.tsrefused
每个路径仅限单一持有者。即将发生冲突的写入会在看板层直接被阻断,而不是落到磁盘上。

各自分支检出的真实代价

每个代理独占一个 worktree 适合在成熟代码库中分别修复三个无关的 Bug。但在协同构建同一个新应用的场景下,这种模式会在四个关键点崩溃:

第二个代理无法读取第一个代理的代码。编写 API 的代理需要数据模型代理一分钟前在另一个分支刚写好的类型,由于看不见,它会重新声明一套。最终导致同一个概念出现多套不同定义,并在合并时产生无法调和的冲突。

在全新项目中,各个代理会同时创建 package.json、tsconfig 和 lockfile 等公共配置文件,产生最大化的合并冲突。

实时预览变得残缺不全:它要么落后于最新进展,要么只能展示三分之一的应用。

最终合并冲突的解决成本高昂,无论是由人类手动解决,还是由额外的大模型消耗 Token 尝试修复。

租约,而非阻塞锁

因此所有代理共享一个目录,通过路径占用机制确保协同安全。占用请求包含路径集合、代理标识、模式和租期。

占用包含两种模式:排他模式(独占写权限)与共享模式(声明只读依赖,在文件发生变更时接收通知)。

export interface ClaimDenial {
  path: string;
  holder: string;
  holderTask: string;
  expiresInS: number;
  hint: string;
}

占用请求绝不阻塞进程。被拒绝的请求会立刻返回持有者信息、当前任务、剩余租期以及下一步建议,代理可以立刻转向其他任务,避免停滞空转浪费 Token。

租约(默认 15 分钟)会在代理每次实际写入时自动顺延。进程退出时,其名下所有租约会立即被服务器自动释放。

自觉遵循只是性能优化手段,服务器强制校验才是正确性的底线保障。

规则由服务器裁决,而非盲目信任

服务器完全掌控文件系统,不会盲信代理的声明。底层文件监视器会捕获每一次写入并严格核验其归属。

classifyWrite(rawPath: string, hintedAgentId?: string): WriteClassification

如果发生越权写入,系统会在冲突变更渲染到屏幕之前自动创建 Git 快照,确保画布提供的回滚功能绝对真实可靠。

系统会立即警告越权代理停止写入并回滚,同时通知原持有者重新读取已被修改的文件。若同一代理违规达三次,系统将暂停代理并由人工介入决策。

天然精准的代码归属记录

由于服务器明确掌握每次写入时的路径持有者,所有的 Git 提交都会自动精确关联到具体负责的代理,无需分叉分支即可在单一主线上获得清晰的责任追踪。

更多架构细节请参阅 多个 AI 代理共用同一个仓库,以及关于远程终端连接的 观察不属于你的终端

直接回答

多个 AI 代理可以同时在同一个代码仓库中工作吗?

可以,前提是必须有机制协调文件写入。在 Murmell 上,代理在修改前声明占用路径,服务器在租约释放前拒绝其他所有代理的写入。

为什么不给每个代理单独分配一个 worktree?

独立分支检出会阻止第二个代理看到第一个代理刚刚写好的类型定义,导致重复编写,且在全新项目中会导致生成的配置文件产生无法自动合并的严重冲突。

占用规则是靠 Prompt 建议还是系统强制执行?

由服务器强制执行。看板直接运行在我们的服务端,坚决拦截未授权的写入,而不是依赖模型可能忽略的提示词约束。