learn-claude-code s12 解析:Worktree + 任务隔离,用 Git Worktree 构建永不碰撞的 Agent 并行执行通道
本篇指南基于 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 文件,含 id、subject、status、worktree 绑定字段 |
.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),天然支持崩溃后重启续编号; - 每个任务文件包含
id、subject、description、status(初始pending)、owner、worktree(初始空串)、blockedBy、created_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 的完整实现包含多层防护,比文档示例更丰富:
- 名称校验:
re.fullmatch(r"[A-Za-z0-9._-]{1,40}", name),只允许 1–40 个字符的字母、数字、.、_、-,防止路径注入; - 查重:若
index.json中已存在同名 worktree 直接报错; - 任务存在性校验:传了
task_id但任务不存在时报错; - 执行 git 命令:
git worktree add -b wt/<name> .worktrees/<name> <base_ref>,分支统一加wt/前缀,base_ref默认为HEAD(可传master、某个 commit 等); - 写注册表 + 回写任务:先追加 entry 到
index.json(含name、path、branch、task_id、status: "active"、created_at),再调用tasks.bind_worktree回写任务文件; - 事件三连:成功路径发
worktree.create.before→worktree.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 /、sudo、shutdown、reboot、> /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.failedworktree.remove.before/worktree.remove.after/worktree.remove.failedworktree.keeptask.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 支持 force 和 complete_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.txt:
anthropic>=0.25.0、python-dotenv>=1.0.0、pyyaml>=6.0; - 环境变量:脚本通过
load_dotenv(override=True)加载.env,必须设置MODEL_ID(源码第 50 行,缺失会直接 KeyError),API key 走ANTHROPIC_API_KEY,可选ANTHROPIC_BASE_URL指向兼容端点; - git 是硬前提:启动时
detect_repo_root用git 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 效果通常更好,也可用中文):
Create tasks for backend auth and frontend login page, then list tasks.Create worktree "auth-refactor" for task 1, then bind task 2 to a new worktree "ui-login".Run "git status --short" in worktree "auth-refactor".Keep worktree "ui-login", then list worktrees and inspect events.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.run的cwd指向 worktree 路径,配合 300s 超时、50000 字符截断和危险命令过滤; - 显式收尾:
keep保留 /remove(complete_task=True)拆除并完成任务,index 中留下kept/removed痕迹供审计; - 事件可观测 + 状态可恢复:
events.jsonl记录全部 before/after/failed 事件;崩溃后凭磁盘文件即可重建,无需依赖会话记忆。
这套"目录级隔离"模式的最小实现不足 800 行(含 agent loop 与工具 schema),是构建任何多任务并行 Agent harness 时值得直接借鉴的参考设计。
atomcodeClaude Code 的开源替代方案。连接任意大模型,编辑代码,运行命令,自动验证 — 全自动执行。用 Rust 构建,极致性能。 | An open-source alternative to Claude Code. Connect any LLM, edit code, run commands, and verify changes — autonomously. Built in Rust for speed. Get StartedRust0623
Hy4-previewHy4 preview 是由腾讯混元团队研发的新一代混合专家(MoE)旗舰模型。模型总参数量 770B,每个 token 激活 49B,主干共包含78层,第一层采用标准 FFN,其余 77 层均为 MoE 结构,每层包含 256 个路由专家与 1 个共享专家,每个 token 激活 top-8 路由专家及共享专家。主干之外原生内置 1 层 MTP(总参数量 10B,激活 0.7B)以支持投机解码。Python00
GLM-5.3GLM-5.3 与 GLM-5.2 使用相同的基座模型——所有提升均来自后训练。与 GLM-5.2 相比,它在复杂编程和长程任务上的表现显著提升。Jinja00
GLM-5.3-FlashGLM-5.3-Flash (320B-A18B),是GLM-5系列的首个原生多模态模型。320B总参数,能力超过GLM-5.2Jinja00
Spark-X2.5-4BSpark-X2.5-4B 旨在让强大的 AI 更实用、更高效、更易获得。在广泛日常任务中表现强劲,涵盖对话、写作、翻译、推理、编码、工具调用以及智能体工作流,并在同等规模的开源模型中取得领先成绩。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00
Spark-X2.5-1.7BSpark-X2.5-1.7B 旨在让强大的 AI 更加实用、高效且易于获取。这些模型在广泛的日常任务中表现出色,涵盖对话、写作、翻译、推理、编程、工具调用和智能体工作流,并在同等规模的开源模型中取得领先结果。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00