并行 worktree · 让子任务在隔离副本里跑

多个 subagent 并行改代码,怎么不互相踩?grok 用 xai-fast-worktree 给每个隔离出一个 git worktree。这一章拆它「快」的几层手段——CoW 反射链接、并行分片拷贝、--no-checkout、以及那个让 git status 瞬间清白的索引 stat-cache 补丁。

作者 David更新于 第 32 篇(共 60 篇)

并行 worktree · 让子任务在隔离副本里跑

subagent 并行干活时有个硬问题:它们都要改代码,怎么不互相踩?grok 的答案是给每个隔离出一个 git worktree——一份独立的工作副本。难点在快:每开一个 subagent 都拷一遍仓库,慢得没法用。xai-fast-worktree 就是把这件事做快。

「快」的几层手段

它不是靠一招,而是层层叠加:

  1. CoW 反射链接(reflink-copy):在 APFS/Btrfs/XFS 上,拷文件只共享数据块、不真复制——这是 macOS 的快路径。
  2. 并行分片拷贝:ignore 并行 walker 遍历,每个文件按 rapidhash(父目录) % worker 数 分片——同目录进同分片,于是建目录不用抢锁。worker 线程跑 clone_file。
  3. git worktree add --no-checkout:让 git 零 checkout、只建元数据,工作树由上面的 CoW 拷贝来填。
  4. 索引 stat-cache 补丁(最关键的技巧):见下。
  5. O(1) 快路径:Linux 依次试 overlayfs 快照 → btrfs 快照 → grove-fuse,macOS 先试 grove-nfs(grove 两条要本机有 Grove 守护进程),都不行才回落到逐文件拷贝。

关键技巧:让 git status 瞬间清白

第 4 点值得单独讲。你把文件 CoW 拷进新 worktree 后,新文件的 inode、ctime 和源索引里记的对不上。git status 一看 stat 缓存全不匹配,就认为每个文件都脏了,于是逐个重新哈希——几万个文件,慢死。

grok 的解法:拷完索引后,update_index_stats 打开拷贝的索引,把每条目的 stat 缓存(mtime/ctime/size/dev/ino……)改写成新拷贝文件的实际元数据。于是 git status 一看全匹配,秒返回「干净」。这一个补丁,把「开个 worktree 后第一次 git status 卡几秒」变成瞬时。 只有干净文件会被打补丁(改过的排除)。

生命周期:建 → 用 → 存

  • 建:WorktreeBuilder::new(source, dest).creation_mode(...).create()(阻塞,调用方 spawn_blocking)。中途用 PartialWorktreeGuard(RAII)——任何 ?/取消/出错,Drop 就把半成品 worktree 拆掉、并从 .git/worktrees/ 注销,不留垃圾。
  • 用:subagent 在里面自由改,和主会话、和别的 subagent 互不干扰。
  • 存:subagent 结束销毁 worktree 前,snapshot_worktree_to_ref 把它的全部改动(含未跟踪文件)快照进一个 git ref(refs/grok/subagents/<id>)——work 不随 worktree 删除而消失,还能 rehydrate 回来。
  • 收:新增的节流式自动 GC(auto_gc.rs)按配置里各类 worktree 的最长存活期回收(手动建的默认永不过期);真要删之前还得过一道「删了会不会丢东西」的闸门(git/safety.rs)——任何失败、超时或读不了的路径,一律判为保留。

谁在用它

  • subagent:每个拿一个 WorktreeKind::Subagent 的 worktree,并行隔离执行。
  • 会话 fork:resume_local_session_in_worktree 把会话复制进新 worktree。
  • worktree 池(已删除):7 月版还有个预建 worktree 的池子,用文件系统 .ready→.claimed 原子重命名做无竞争领取。新代码把它删了(配置注释说它从未接进生产),只留下清理残留池目录的 cleanup_stale_pool_worktrees,[worktree_pool] 配置段也标成了废弃。

和 hunk-tracker 的接缝

还记得 hunk 追踪按绝对路径存改动 review 状态吗?fork 到新 worktree(新 cwd)后,那些绝对路径全指向旧目录了。HunkTrackerSnapshot::rewrite_paths(old_cwd, canonical_old_cwd, new_cwd) 就是那道接缝——把每个 file_states 键和 hunk.path 从旧前缀重写到新前缀(还处理 macOS /var→/private/var 符号链接差异),让 fork 出来的会话的 hunk 状态对齐到新副本。两个子系统之间这种「路径重写」的接缝,是并行/隔离设计里必须处理的琐碎但要命的细节。

和《从 LLM 到 Coding Agent》对照

对照 subagents:最小实现里 subagent 就是「另开一个上下文窗口」,不涉及文件隔离。grok 的增量是真让它们在隔离的文件副本里跑——于是要解决「怎么开得快」(CoW + 分片 + stat-cache 补丁)、「怎么不留垃圾」(RAII 守卫)、「work 怎么不丢」(快照进 ref)、「状态怎么跟着搬」(路径重写)。这是把「并行」从「并行推理」落到「并行改文件」的全部工程代价。

Part 3 工作区与 Git 到此为止。下一部分扩展机制,看 grok 怎么被 AGENTS.md、Skills、MCP、Hooks、Plugins 扩展。


上一篇:图谱的真实用途

本页目录