多个 Agent 在同一工作目录并行修改代码时,最直接的风险是互相覆盖:一个正在重命名文件,另一个仍按旧路径编辑;两个测试同时改生成物;一个任务的 git status 混入另一个任务的变更。
Git worktree 允许同一仓库同时拥有多个工作目录,每个 linked worktree 通常检出不同分支:
repository
├── main worktree → main
└── .worktrees/
├── auth-refactor → wt/auth-refactor
└── api-tests → wt/api-tests将任务与 worktree 绑定后,Agent 的文件和命令工具从任务 assignment 中取得工作目录。任务管“做什么”,worktree 管“在哪里做”。
Worktree 共享什么,隔离什么
Linked worktree 不是完整仓库副本。它们共享 Git 对象库和大部分 refs,但拥有各自的:
- 工作目录;
HEAD;- index;
- 当前检出分支状态;
- 未提交文件修改。
因此,两个任务可以分别修改同名文件,而不会直接写进同一个 checkout。
不过它们仍然共享很多资源:
- 同一 Git repository 的对象和分支引用;
- 默认共享的 Git config;
- 父进程可访问的文件系统和凭据;
- 数据库、网络、端口与外部服务;
- 包管理器缓存、容器名称和其他全局资源。
Worktree 是 Git 工作目录隔离,不是 OS 沙箱、容器或权限边界。
先识别当前 Git 仓库
一个常见入口是:
git rev-parse --show-toplevel它返回当前工作树的顶层目录;命令失败通常意味着当前目录不在可用 Git 工作树中,或 Git 环境异常。
调用方不能把所有失败都静默解释为“不是 Git 仓库”。至少应区分:
- Git 命令不存在;
- 当前目录不是仓库;
- 命令超时;
- 权限错误;
- 仓库元数据损坏。
在 linked worktree 中,--show-toplevel 返回当前 worktree 顶层,不是主 worktree。若需要共享 Git 元数据目录,应使用 Git 提供的 --git-common-dir 等接口,而不是猜 .git 是目录;linked worktree 中的 .git 通常是指向共享元数据的文件。
创建前必须明确起始状态
典型创建命令是:
git worktree add -b wt/auth-refactor .worktrees/auth-refactor <start-point>它创建新分支,并在新目录中检出指定 commit 或 ref。
最容易被忽略的一点是:worktree 基于 Git commit/ref 创建,不会自动携带主工作区尚未提交的修改。假设主目录有未提交的新接口,Agent 从 HEAD 创建 worktree 后,那里仍然看不到这项改动。
因此宿主必须显式选择 starting state:
- 已提交基线:从明确 branch、tag 或 commit 创建;
- 当前工作树状态:先由用户决定提交、制作 patch、stash 或采用产品支持的 working-tree 快照;
- 新分支:明确从哪个已提交基线创建;
- 依赖另一个任务:等待上游分支集成,或选择其 commit 作为起点。
不能静默假设 HEAD 就代表用户当前看到的代码。
分支和路径都需要稳定命名
任务名可能包含空格、斜杠、.. 或其他路径字符,不能直接拼接为目录和分支。运行时应分别验证:
- worktree 逻辑名称;
- 目标目录位于允许的根目录内;
- 分支名通过
git check-ref-format --branch; - 路径没有与现有 worktree 或普通文件冲突;
- 名称与已有任务绑定不重复。
当前课程使用受限名称并创建 wt/<name> 分支。Git 默认拒绝把已经在其他 worktree 检出的同一分支再次检出;不要用 --force 绕过这条保护,除非调用者理解多个目录操作同一分支引用的后果。
如果自动生成名称,还要记录人类可读任务 ID 与真实分支、路径之间的映射,不能依赖目录名反推所有权。
任务绑定应晚于 Git 创建成功
安全顺序是:
校验任务 pending、无人认领、尚未绑定
↓
校验名称、分支、路径与起始 ref
↓
执行 git worktree add
↓
用 git worktree list 核验注册信息
↓
最后把 worktree 写入任务记录若先写任务绑定,再执行 Git 命令,创建失败会让任务指向不存在的目录。
反过来也可能出现部分成功:Git 报错,但分支已经创建,或者 worktree 已注册、目录也已留下。此时不能假装什么都没发生,也不应未经检查自动删除。运行时应报告 partial operation,列出已存在的分支、目录和 Git 注册项,让宿主决定恢复还是清理。
创建接口最好支持幂等重试:相同 task、name、start point 重试时,若现有状态完全匹配就返回已有绑定;状态不一致则停止并报告冲突。
Git 自己的注册信息才是权威来源
旧版实现维护 .worktrees/index.json。额外索引可以记录任务 ID、创建者和策略,但可能与 Git 元数据漂移。
检查真实 worktree 时应优先使用:
git worktree list --porcelain -z--porcelain 提供面向脚本的稳定格式,-z 使用 NUL 分隔,能安全处理包含换行等特殊字符的路径。
应用索引适合保存 Git 不知道的业务字段;Git worktree list 适合确认路径、HEAD、branch、locked 和 prunable 状态。两者不一致时应进入 reconciliation,而不是任选一边覆盖另一边。
Claim 时建立 cwd lease
任务绑定 worktree 后,Agent 还不能任意操作。当前课程在 claim 成功时解析任务目录,并建立 assignment:
owner → {task_id, cwd}文件读取、写入、编辑、glob 和 Bash 都从这份 assignment 取得 cwd。没有认领任务的 teammate 不允许使用工作区工具;绑定失效时也不会回退到主仓库。
这份映射可以理解为 cwd lease:
- 它只对当前 owner 和 task 有效;
- 换任务后必须失效;
- 进程恢复时要从任务记录重新核验;
- 路径必须仍是 Git 注册的目标 worktree;
- owner 身份应由运行时提供,不能由模型伪造。
仅把 worktree 路径写进 system prompt 不够,真正的工具 handler 必须使用受验证的 cwd。
为什么完成任务后不能立刻释放 cwd
一次模型响应可能包含:
complete_task
read_file 验证产物
git status 检查状态如果 complete_task handler 立刻删除 assignment,后续同批工具会失去目录,甚至回退到错误位置。
当前课程在任务完成后保留 cwd,直到这一模型轮次结束、队友回到 IDLE 才释放。完成失败时也继续保留,便于 Agent 修正后重试。
这体现了一条通用规则:状态切换要考虑工具批次边界。模型在同一响应中已经生成的后续调用,不会因为前一个 handler 改变状态而自动重写参数。
并行任务仍可能产生逻辑冲突
不同 worktree 避免了直接文件覆盖,却不能避免两个任务同时修改同一接口。最终合并时仍可能出现:
- 文本冲突;
- 没有文本冲突但语义不兼容;
- 数据库迁移顺序冲突;
- 测试分别通过、合并后失败;
- 生成物和依赖锁文件冲突。
任务拆分时应明确模块所有权和接口契约。高耦合文件可以只分配给一个任务,其他队友通过消息提出修改需求。
即使每个 worktree 都通过测试,最终仍要在目标集成分支上重新运行完整验证。
Worktree 不隔离运行时资源
两个 Agent 分别在不同目录执行:
pnpm dev仍可能争用同一个端口。类似问题还包括:
- 相同数据库和测试数据;
- 相同 Docker Compose project 名;
- 相同云环境;
- 相同临时目录;
- 相同缓存或输出路径;
- 相同凭据和速率限制。
完整任务隔离需要为这些资源分配命名空间,例如独立端口、数据库 schema、Compose project、临时目录和测试账号。Worktree 只能解决 Git checkout 这一层。
依赖与构建缓存如何处理
新 worktree 拥有自己的文件目录,因此通常需要自己的 node_modules、虚拟环境或构建输出。包管理器可以共享全局内容寻址 Store,但项目级链接和生成物仍应在各自工作目录建立。
不要直接让多个 worktree 共享一个可变 node_modules 或构建目录,除非工具明确支持并发和路径差异。否则一个任务升级依赖时,另一个任务的运行结果会变得不可重现。
缓存键至少应考虑 lockfile、平台、运行时版本和任务分支状态。
Event log 用于审计,不是事实源
Worktree 生命周期适合记录事件:
{
"event": "worktree.created",
"timestamp": "...",
"taskId": "task_a1b2c3d4",
"path": ".worktrees/auth-refactor",
"branch": "wt/auth-refactor",
"startPoint": "abc123..."
}还可以记录 create requested、partial failure、claimed、completed、released、integration verified 和 removed。
JSONL 适合追加和人工检查,但并不会天然可靠:并发写入需要锁,崩溃可能留下不完整行,读取时跳过坏行会损失审计信息。高要求系统应使用具备事务与访问控制的事件存储。
事件回答“发生过什么”,Git 注册信息回答“现在有哪些 worktree”,任务 Store 回答“业务上绑定给谁”。三者职责不同。
任务完成不等于成果已经集成
一个任务可以在自己的分支上完成,但主分支仍然看不到这些修改。收尾至少分成:
task work completed
↓
worktree 内验证通过
↓
审查 diff 与提交
↓
合并、rebase 或 cherry-pick 到集成分支
↓
在集成状态重新验证
↓
任务验收完成是否允许 Agent 自动提交、合并或解决冲突,应由项目工作流和用户授权决定。完成任务不应暗中执行 Git push 或删除分支。
如果没有提交,移除 worktree 可能直接丢失成果;如果已经提交但未合并,分支必须保留并明确记录。
安全移除的默认策略
Git 官方行为是:git worktree remove 默认只移除干净的 linked worktree;有 tracked 修改或 untracked 文件时会拒绝,--force 可以覆盖这一保护。主 worktree不能被该命令移除。
自动化宿主应在调用前做更严格的预检:
- 任务不能仍为 pending 或 in-progress;
- 没有活跃 cwd lease;
git status --porcelain没有 tracked、untracked 或需要保留的 ignored 文件;- 重要提交已经集成或明确保留分支;
- 用户知道将删除哪个目录;
- 路径仍位于受控 worktree 根目录;
- Git 注册信息与业务索引一致。
不要把 --force 当作常规清理。带丢弃改动的移除属于破坏性操作,必须由用户明确批准,并在操作后报告哪些内容不可恢复。
移除 worktree 不会删除分支
git worktree remove .worktrees/auth-refactor移除的是 linked worktree 目录和对应管理记录,不会自动执行:
git branch -d wt/auth-refactor保留分支通常是更安全的默认值,尤其当分支含有尚未合并或没有 upstream 的提交。
分支删除应是独立决策:先确认成果已集成且分支不再需要,再由用户或受控宿主执行。强制删除分支与强制移除目录不应合并成一个模糊的“cleanup=true”。
Lock、prune 与 repair 解决什么
Git 还提供几个生命周期工具:
git worktree lock --reason ...:防止可移动磁盘或暂时离线路径的管理记录被 prune,也阻止普通 move/remove;git worktree prune --dry-run:预览会清理哪些缺失 worktree 的陈旧管理记录;git worktree repair:主仓库或 worktree 被手工移动后,尝试修复双方连接;git worktree list --porcelain:检查 locked、prunable、branch 和 HEAD。
不要通过文件系统直接删除 .worktrees/<name> 来代替 git worktree remove。手工删除会留下 Git 管理记录,后续只能 prune 或 repair。
Locked worktree 的强制移除需要更强覆盖,正说明 lock 是防误操作机制,不是安全权限系统。
Submodule 和配置边界
Git 官方文档说明 multiple checkout 对 submodule 的支持仍不完整,不建议把包含 submodule 的 superproject 随意做多份 checkout。采用前应在目标仓库和 Git 版本上实际测试初始化、更新、移动和清理流程。
Git config 默认在 worktree 之间共享。需要不同配置时,可以使用 Git 的 worktree-specific config 机制,但启用方式和兼容性要明确。不要以为在一个 worktree 执行普通 git config 一定只影响该目录。
建议覆盖的测试
Worktree Task Isolation 至少应验证:
- 非 Git 目录、Git 不可用、超时和损坏元数据;
- 主 worktree 与 linked worktree 的根目录识别;
- 明确 branch、commit 和 working-tree snapshot 起点;
- 主目录未提交修改不会静默出现在新 worktree;
- 名称、分支和目标路径校验;
- 同一分支已在其他 worktree 检出时拒绝;
- Git 创建成功后才写任务绑定;
- 分支已创建、worktree 未完成等 partial failure;
- 重试的幂等性与状态冲突;
- Git worktree list 与应用索引 reconciliation;
- claim 建立正确 cwd lease,无任务时工具被拒绝;
- worktree 失效时不回退主仓库;
- complete 后在工具批次结束前保留 assignment;
- 多个 worktree 修改相同接口的合并与语义冲突;
- 端口、数据库、容器、缓存和凭据冲突;
- worktree 内验证与集成分支验证分开;
- dirty、untracked、ignored、locked 和含 submodule 的移除;
- remove 后分支仍然存在;
- prune dry-run、repair 和异常移动;
- 强制丢弃必须有精确用户批准和审计记录。
小结
Git worktree 为 Agent 任务提供的是独立 checkout:每个任务可以绑定自己的分支和目录,claim 后由 cwd lease 确保工具落在正确位置。它能显著减少并行文件覆盖,却不会继承主工作区未提交改动,也不隔离端口、数据库、凭据和系统权限。
可靠生命周期需要明确起始 commit、创建成功后再绑定、识别部分失败、在任务目录与集成状态分别验证,并把任务完成、成果集成、worktree 移除和分支删除拆成独立动作。清理默认保守,任何强制丢弃都应回到用户手里。