首页
/ learn-claude-code s12 解析:Worktree + 任务隔离,用 Git Worktree 构建永不碰撞的 Agent 并行执行通道

learn-claude-code s12 解析:Worktree + 任务隔离,用 Git Worktree 构建永不碰撞的 Agent 并行执行通道

2026-09-06 20:14:01作者:明树来

本篇指南基于 learn-claude-code("Bash is all you need" 的 nano claude-code agent harness 教学仓库)中第 s12 课的核心文档,深入讲解如何用 git worktree 为每个任务分配独立的执行目录,解决多 Agent 并行开发时的文件互相污染问题。读完后,你将理解"任务控制平面 + 目录执行平面"的双状态机设计、任务与 worktree 的 ID 绑定机制、生命周期事件流的实现细节,并能在真实仓库中运行这套隔离方案。

1. 问题背景:共享目录下的并行碰撞

s12 处于仓库旧版 12 课渐进式课程线的终点附近:到 s11 为止,Agent 已经具备自主认领(claim)和完成任务的能力,任务板(task board)负责"做什么"。但所有任务都运行在同一个共享目录里,文档中给出了一个典型的失败场景:

两个 Agent 同时重构不同模块——Agent A 改 config.py,Agent B 也改 config.py,未提交的改动互相混合(unstaged changes mix),谁也没法干净地回滚。

任务板只追踪 what to do,对 where to do it 没有任何约束。s12 的解法一句话概括:给每个任务一个独立的 git worktree 目录,任务管目标,worktree 管执行上下文,用任务 ID 把两者绑定起来("Isolate by directory, coordinate by task ID")。

对应实现位于 agents/s12_worktree_task_isolation.py,英文文档见 docs/en/s12-worktree-task-isolation.md,中文对照见 docs/zh/s12-worktree-task-isolation.md

2. 整体架构:双平面 + 双状态机

文档给出的架构全景图如下,左半部分是控制平面.tasks/,任务状态),右半部分是执行平面.worktrees/,真实的工作目录),中间通过 task_id 双向关联:

Control plane (.tasks/)             Execution plane (.worktrees/)
+------------------+                +------------------------+
| task_1.json      |                | auth-refactor/         |
|   status: in_progress  <------>   branch: wt/auth-refactor
|   worktree: "auth-refactor"   |   task_id: 1             |
+------------------+                +------------------------+
| task_2.json      |                | ui-login/              |
|   status: pending    <------>     branch: wt/ui-login
|   worktree: "ui-login"       |   task_id: 2             |
+------------------+                +------------------------+
                                    |
                          index.json (worktree registry)
                          events.jsonl (lifecycle log)

State machines:
  Task:     pending -> in_progress -> completed
  Worktree: absent  -> active      -> removed | kept

两个状态机分别管理各自的生命周期,磁盘上的三类持久化文件是恢复的依据:

文件 角色
.tasks/task_N.json 每个任务一个 JSON 文件,含 idsubjectstatusworktree 绑定字段
.worktrees/index.json worktree 注册表,记录名称、路径、分支、task_id、状态
.worktrees/events.jsonl 追加式生命周期事件日志

从源码结构看,崩溃后不需要会话内存——.tasks/ + .worktrees/index.json 即可重建全部现场。文档的原话是:"Conversation memory is volatile; file state is durable."(会话记忆是易失的,磁盘状态是持久的。)

3. 工作原理:五步完整流程

3.1 第一步:先创建任务,持久化目标

TASKS.create("Implement auth refactor")
# -> .tasks/task_1.json  status=pending  worktree=""

TaskManager 的实现细节:

  • 任务 ID 通过扫描目录下已有的 task_*.json 取最大 ID + 1 得到(_max_id),天然支持崩溃后重启续编号;
  • 每个任务文件包含 idsubjectdescriptionstatus(初始 pending)、ownerworktree(初始空串)、blockedBycreated_at/updated_at 等字段(见 create);
  • status 只允许 pending / in_progress / completed 三个值,update 方法会显式校验非法状态并抛出 ValueError源码)。

3.2 第二步:创建 worktree 并绑定任务

WORKTREES.create("auth-refactor", task_id=1)
# -> git worktree add -b wt/auth-refactor .worktrees/auth-refactor HEAD
# -> index.json gets new entry, task_1.json gets worktree="auth-refactor"

传入 task_id 会自动把任务从 pending 推进到 in_progress。绑定逻辑的核心代码(文档摘录,与 TaskManager.bind_worktree 实现一致):

def bind_worktree(self, task_id, worktree):
    task = self._load(task_id)
    task["worktree"] = worktree
    if task["status"] == "pending":
        task["status"] = "in_progress"
    self._save(task)

WorktreeManager.create 的完整实现包含多层防护,比文档示例更丰富:

  1. 名称校验re.fullmatch(r"[A-Za-z0-9._-]{1,40}", name),只允许 1–40 个字符的字母、数字、._-,防止路径注入;
  2. 查重:若 index.json 中已存在同名 worktree 直接报错;
  3. 任务存在性校验:传了 task_id 但任务不存在时报错;
  4. 执行 git 命令git worktree add -b wt/<name> .worktrees/<name> <base_ref>,分支统一加 wt/ 前缀,base_ref 默认为 HEAD(可传 master、某个 commit 等);
  5. 写注册表 + 回写任务:先追加 entry 到 index.json(含 namepathbranchtask_idstatus: "active"created_at),再调用 tasks.bind_worktree 回写任务文件;
  6. 事件三连:成功路径发 worktree.create.beforeworktree.create.after,任何异常则发 worktree.create.failed 并向上抛出。

注意一个从源码结构可以看出的一致性策略:create 失败时不会留下半绑定状态——index 只在 git 成功后写入,任务绑定也紧随其后,异常直接抛出由上层(agent loop 的 handler)捕获为错误文本。

3.3 第三步:在 worktree 中执行命令

subprocess.run(command, shell=True, cwd=worktree_path,
               capture_output=True, text=True, timeout=300)

对应 WorktreeManager.run,隔离的本质就是 cwd 参数指向了隔离目录。实现的额外细节:

  • 命令先经过一个危险命令黑名单过滤(rm -rf /sudoshutdownreboot> /dev/),命中即返回 Error: Dangerous command blocked
  • worktree 名称不存在或路径被删时返回明确错误,而不是静默执行;
  • 超时 300 秒,超时返回 Error: Timeout (300s)
  • 输出合并 stdout + stderr 后截断到 50000 字符,防止撑爆上下文窗口。

配套的 status 方法(源码)在指定 worktree 内执行 git status --short --branch,工作区干净时返回 "Clean worktree"。

3.4 第四步:收尾——keep 或 remove

任务结束后有两个显式出口:

  • worktree_keep(name):目录保留供后续使用,index.json 中状态置为 kept 并记录 kept_at源码),同时发出 worktree.keep 事件;
  • worktree_remove(name, complete_task=True):删除目录、完成绑定任务、发出事件,一个调用搞定"拆除 + 完成"。

文档摘录的 remove 核心逻辑与 WorktreeManager.remove 一致:

def remove(self, name, force=False, complete_task=False):
    self._run_git(["worktree", "remove", wt["path"]])
    if complete_task and wt.get("task_id") is not None:
        self.tasks.update(wt["task_id"], status="completed")
        self.tasks.unbind_worktree(wt["task_id"])
        self.events.emit("task.completed", ...)

实际源码在此之外还做了三件事:force=True 时给 git 追加 --force 参数;拆除后把 index 中对应条目的 status 置为 removed 并记录 removed_at(条目保留而非删除,注册表可追溯历史);失败时发 worktree.remove.failed 事件。注意 complete_task 会同时调用 unbind_worktree 清空任务上的 worktree 字段,保持两侧引用一致。

3.5 第五步:事件流(Event Bus)

每个生命周期步骤都追加写入 .worktrees/events.jsonl,例如:

{
  "event": "worktree.remove.after",
  "task": {"id": 1, "status": "completed"},
  "worktree": {"name": "auth-refactor", "status": "removed"},
  "ts": 1730000000
}

EventBus 是 append-only 的:emit{event, ts, task, worktree[, error]} 序列化为一行 JSON 追加到文件;list_recent(limit) 读取最后 N 行(钳制在 1–200 之间),解析失败的行会标记为 parse_error 而不是抛异常。完整事件类型清单:

  • worktree.create.before / worktree.create.after / worktree.create.failed
  • worktree.remove.before / worktree.remove.after / worktree.remove.failed
  • worktree.keep
  • task.completed

4. 对模型暴露的工具面

s12 的 agent loop(agent_loop)把上述能力封装为 17 个工具注册进 TOOLS 列表和 TOOL_HANDLERS 分发表,分为四组:

工具 说明
基础 bash / read_file / write_file / edit_file 与工作区同风格的原子文件与 shell 工具,bash 超时 120s、同样有危险命令过滤
任务平面 task_create / task_list / task_get / task_update / task_bind_worktree 任务板 CRUD;task_list 输出形如 [>] #1: subject owner=x wt=auth-refactor,一眼看到状态、负责人和 worktree 绑定
执行平面 worktree_create / worktree_list / worktree_status / worktree_run / worktree_keep / worktree_remove 每个 worktree 操作都受 index.json 约束,worktree_remove 支持 forcecomplete_task 布尔参数
可观测 worktree_events 读取最近 N 条生命周期事件

系统提示词(SYSTEM)明确引导模型的行为模式:"For parallel or risky changes: create tasks, allocate worktree lanes, run commands in those lanes, then choose keep/remove for closeout."——即把 worktree 当作"并行执行通道"(execution lanes)来使用。

5. 相对 s11 的变化

文档的对比表完整继承了 s12 的增量价值,这里原文保留:

Component Before (s11) After (s12)
Coordination Task board (owner/status) Task board + explicit worktree binding
Execution scope Shared directory Task-scoped isolated directory
Recoverability Task status only Task status + worktree index
Teardown Task completion Task completion + explicit keep/remove
Lifecycle visibility Implicit in logs Explicit events in .worktrees/events.jsonl

6. 运行与验证

6.1 环境要求

  • Python 依赖见 requirements.txtanthropic>=0.25.0python-dotenv>=1.0.0pyyaml>=6.0
  • 环境变量:脚本通过 load_dotenv(override=True) 加载 .env,必须设置 MODEL_ID源码第 50 行,缺失会直接 KeyError),API key 走 ANTHROPIC_API_KEY,可选 ANTHROPIC_BASE_URL 指向兼容端点;
  • git 是硬前提:启动时 detect_repo_rootgit rev-parse --show-toplevel 定位仓库根,WorktreeManager 再用 git rev-parse --is-inside-work-tree 检查 git_available。不在 git 仓库内时脚本仍能启动,但所有 worktree_* 工具会返回 "Not in a git repository. worktree tools require git." 错误。

6.2 运行

cd learn-claude-code
python agents/s12_worktree_task_isolation.py

启动后进入交互终端(提示符 s12 >>),文档建议依次输入以下五个 prompt(英文 prompt 效果通常更好,也可用中文):

  1. Create tasks for backend auth and frontend login page, then list tasks.
  2. Create worktree "auth-refactor" for task 1, then bind task 2 to a new worktree "ui-login".
  3. Run "git status --short" in worktree "auth-refactor".
  4. Keep worktree "ui-login", then list worktrees and inspect events.
  5. Remove worktree "auth-refactor" with complete_task=true, then list tasks/worktrees/events.

这五步恰好覆盖"创建 → 绑定 → 隔离执行 → 保留 → 拆除+完成"的完整生命周期,运行结束后检查 .tasks/.worktrees/index.json.worktrees/events.jsonl 即可验证每一步的落盘状态。

7. 与当前 17 课体系的衔接

需要说明版本背景:README.md 记录了课程的重构映射——旧版 12 课中的 "Task-bound worktrees"(old s12)在新 17 课体系中被并入 s13 Agent Teams("persistent teammates / atomic task claims / task-bound worktrees / typed protocols"),README 在 Claude Code 架构拆解中也把 "task-bound worktrees for parallel edits" 列为 harness 的组成部分。也就是说,s12 这套 worktree 隔离机制并没有被抛弃,而是作为多 Agent 协作(s13)的一项基础能力被延续。

从测试代码可以印证这一演进:tests/test_agent_teams_runtime.py 中存在大量 worktree 相关用例,例如队友持有过期 worktree 分配时的容错(test_teammate_survives_stale_worktree_assignment)、脏工作区默认拒绝删除(test_remove_worktree_refuses_dirty_checkout_by_default)、非法路径(../escape)永远不可认领(test_invalid_or_unregistered_worktree_never_becomes_claimable)等。可以推断,s12 在单机场景建立的"注册表 + 状态机 + 事件流"三件套,是 s13 多 Agent 场景下更复杂治理规则的地基。深入该主题可继续阅读 s13_agent_teams/README.md

8. 要点回顾

  • 分离两个平面.tasks/ 管目标与状态(控制平面),.worktrees/ 管目录与分支(执行平面),用 task_id 双向绑定;
  • 绑定即推进create(name, task_id=...) 一次调用完成 git worktree 创建、index 登记、任务状态 pending → in_progress 三件事,绑定写两侧;
  • 隔离靠 cwd:执行工具只是把 subprocess.runcwd 指向 worktree 路径,配合 300s 超时、50000 字符截断和危险命令过滤;
  • 显式收尾keep 保留 / remove(complete_task=True) 拆除并完成任务,index 中留下 kept / removed 痕迹供审计;
  • 事件可观测 + 状态可恢复events.jsonl 记录全部 before/after/failed 事件;崩溃后凭磁盘文件即可重建,无需依赖会话记忆。

这套"目录级隔离"模式的最小实现不足 800 行(含 agent loop 与工具 schema),是构建任何多任务并行 Agent harness 时值得直接借鉴的参考设计。

登录后查看全文
热门项目推荐
相关项目推荐