learn-claude-code s12 解析:Worktree + 任务隔离,用 Git Worktree 构建永不碰撞的 Agent 并行执行通道
【免费下载链接】learn-claude-codeBash is all you need - A nano claude code–like 「agent harness」, built from 0 to 1项目地址: https://gitcode.com/GitHub_Trending/an/learn-claude-code
本篇指南基于 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 时值得直接借鉴的参考设计。
【免费下载链接】learn-claude-codeBash is all you need - A nano claude code–like 「agent harness」, built from 0 to 1项目地址: https://gitcode.com/GitHub_Trending/an/learn-claude-code
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考