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

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

- 作者：David（道雾轩）
- 专栏：Grok Build 源码解读（https://daiw.org/manual/grok-build.md）
- 最后更新：2026-09-15
- 原文：https://daiw.org/manual/grok-build/fast-worktree
- 转载与引用：请注明出处并附原文链接（https://daiw.org/about/copyright）

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

[subagent](https://daiw.org/manual/grok-build/subagents) 并行干活时有个硬问题：它们都要改代码，怎么不互相踩？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 追踪](https://daiw.org/manual/grok-build/hunk-tracker)按**绝对路径**存改动 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](https://daiw.org/manual/llm-to-agent/subagents)：最小实现里 subagent 就是「另开一个上下文窗口」，不涉及文件隔离。grok 的增量是**真让它们在隔离的文件副本里跑**——于是要解决「怎么开得快」(CoW + 分片 + stat-cache 补丁)、「怎么不留垃圾」(RAII 守卫)、「work 怎么不丢」(快照进 ref)、「状态怎么跟着搬」(路径重写)。这是把「并行」从「并行推理」落到「并行改文件」的全部工程代价。

Part 3 工作区与 Git 到此为止。下一部分[扩展机制](https://daiw.org/manual/grok-build/agents-md)，看 grok 怎么被 AGENTS.md、Skills、MCP、Hooks、Plugins 扩展。

---

上一篇：[图谱的真实用途](https://daiw.org/manual/grok-build/codebase-graph-use)
