HARNESS 112

Worktree + Task Isolation

"各干各的目录,互不干扰"

s07 的任务板管"做什么",但不管"在哪做"。
s12 用 Git Worktree 给每个任务一个独立目录——用 task_id 把控制面和执行面绑定在一起。

1

s11 的 Agent 已经能自主干活——还缺什么?

到 s11,Agent 已经能扫描任务池、认领任务、自主执行。但所有任务共享一个目录。两个 Agent 同时重构不同模块——A 改 config.py,B 也改 config.py,未提交的改动互相污染,谁也没法干净回滚。

s11:共享目录

🔹 任务板只管"做什么"

🔹 没有"在哪做"的概念

🔹 Agent A 和 B 在同一个目录改文件

🔹 未提交的改动互相覆盖

🔹 无法单独回滚某个任务

s12:任务-目录绑定

🔹 控制面(.tasks/)管"做什么"

🔹 执行面(.worktrees/)管"在哪做"

🔹 每个任务一个独立 git worktree 目录

🔹 用 task_id 把两面绑在一起

🔹 每个任务的改动在独立分支,干净回滚

不只是"文件冲突"——共享目录的三个深层问题

① 测试污染:Agent A 跑测试时,目录里有 Agent B 未完成的改动。测试结果不可信。

② 回滚灾难:A 的改动有问题要 git reset --hard,但 B 的改动也在里面——怎么只回滚 A?

③ 崩溃恢复缺失:Agent 崩溃后,共享目录是脏的。"谁改了哪些文件?哪些是已完成任务的,哪些是进行中的?"——没有答案,因为没有把任务和目录关联起来。

核心洞见:任务板管"做什么"(控制面),但不管"在哪做"(执行面)。s12 补上这个缺——每个任务绑定一个独立 worktree 目录,用 task_id 关联两面。
2

解决方案:双面 — 控制面 + 执行面

s12 的核心结构:两个平面通过 task_id ↔ worktree name 绑定。任务从 pending → in_progress → completed,worktree 从 absent → active → removed | kept。

🎯 控制面 (.tasks/)
task_1.json
  title: "Implement auth refactor"
  status: in_progress
  worktree: "auth-refactor"
  owner: "alice"

task_2.json
  title: "Build UI login page"
  status: pending
  worktree: ""
  owner: ""
📂 执行面 (.worktrees/)
auth-refactor/
  branch: wt/auth-refactor
  task_id: 1

ui-login/
  branch: wt/ui-login
  task_id: 2

index.json (registry)
events.jsonl (日志)

两个状态机,一个绑定

📋 Task
pending
📋 Task
in_progress
📋 Task
completed
↕ bind_worktree(task_id, name)
📂 Worktree
absent
📂 Worktree
active
📂 Worktree
removed | kept

两个状态机独立但联动。当 worktree 被创建并绑定任务 → 任务自动从 pending 推进到 in_progress。当 worktree 被 remove 且 complete_task=true → 任务从 in_progress 推进到 completed。

和 s07 的任务图是什么关系?

s07 的任务文件(.tasks/task_*.json)新增一个字段:"worktree": "auth-refactor"。之前 s07 只管 status、owner、blockedBy——现在多了一个 worktree 字段记录"这个任务在哪个目录里执行"。同一个 task_*.json 文件,s12 只是多写了一个字段。

向后兼容:老任务的 worktree 字段是空字符串——就是 s11 的行为(共享目录)。新任务带上 worktree 字段就享受隔离。

3

创建 Worktree + 绑定任务:一步到位

create():创建目录 + 创建分支 + 绑定任务

📝 中文注释版
def create(self, name, task_id=None): wt_path = self.dir / name branch = f"wt/{name}" # ① git worktree add — 秒级创建 self._run_git(["worktree", "add", "-b", branch, str(wt_path), "HEAD"]) # ② 写入 registry (index.json) entry = {"name": name, "path": str(wt_path), "branch": branch, "task_id": task_id} self.index["worktrees"].append(entry) self._save_index() # ③ 绑定任务(如果传了 task_id) if task_id is not None: self.bind_worktree(task_id, name) self.events.emit("worktree.create.after", ...) return wt_path
💻 英文代码原版
def create(self, name, task_id=None): wt_path = self.dir / name branch = f"wt/{name}" self._run_git(["worktree", "add", "-b", branch, str(wt_path), "HEAD"]) entry = {"name": name, "path": str(wt_path), "branch": branch, "task_id": task_id} self.index["worktrees"].append(entry) self._save_index() if task_id is not None: self.bind_worktree(task_id, name) self.events.emit("worktree.create.after", ...) return wt_path

bind_worktree():写入两侧状态

📝 中文注释版
def bind_worktree(self, task_id, worktree): task = self.tasks._load(task_id) # 写入任务侧:记录在哪个 worktree 执行 task["worktree"] = worktree # 自动推进状态 if task["status"] == "pending": task["status"] = "in_progress" self.tasks._save(task) # 持久化
💻 英文代码原版
def bind_worktree(self, task_id, worktree): task = self.tasks._load(task_id) task["worktree"] = worktree if task["status"] == "pending": task["status"] = "in_progress" self.tasks._save(task)

bind_worktree 是 s12 的引擎——它同时做了两件事:① 在 task 上记录 worktree 字段(控制面知道自己映射到哪个执行面);② 把 pending 任务推进到 in_progress(worktree 创建 = 任务开始执行)。一个函数调用,两侧状态同步写入。

4

在 worktree 里执行:cwd 隔离,Agent 无感

Agent 不需要知道自己在一个 worktree 里。它照常调 Bash 工具执行命令——但 cwd 指向了隔离目录。

subprocess.run 的 cwd 参数

def run_in_worktree(self, name, command, timeout=300): wt = self.index["worktrees"][name] # 查 registry 找到 worktree 路径 worktree_path = wt["path"] result = subprocess.run( command, shell=True, cwd=worktree_path, # ⭐ 关键:所有命令在隔离目录里执行 capture_output=True, text=True, timeout=timeout ) return result

Agent 的视角:Agent 调 bash("python test.py"),看到的输出是 worktree 里的测试结果。Agent 读文件、写文件——cwd 保证这些操作都在隔离目录里。对 Agent 的代码逻辑零侵入。

对比:s11 的相同命令在共享目录执行(cwd 是主项目路径)。s12 只改了一行——cwd 指向 worktree。一行的改动,隔离了整个执行环境。

🔬 不止文件隔离——分支也隔离

每个 worktree 在独立分支上(wt/auth-refactor)。Agent 在 worktree 里做 git add + git commit 只影响自己的分支。不会污染 main 分支,也不会看到其他 Agent 未合并的 commit。

这意味着一件事:Lead 可以逐个审查每个 Agent 的工作成果——git log wt/auth-refactor 看 Alice 做了哪些 commit,git diff main..wt/auth-refactor 看她改了什么。Agent 的工作变成了可审查、可单独合并的 Git 分支。

5

收尾:keep 保留 vs remove 删除 + 完成任务

任务做完后,worktree 有两种命运——保留供后续使用,或删除并完成绑定任务。

worktree_keep(name)

🔹 保留目录和分支

🔹 任务状态不变

🔹 后续可以再进入继续工作

🔹 适用:任务需要多轮迭代

worktree_remove(name, complete_task=True)

🔹 删除目录

🔹 自动把绑定任务标为 completed

🔹 一个调用搞定拆除 + 完成

🔹 发出 task.completed 事件

remove():拆除 + 完成任务 + 发出事件

📝 中文注释版
def remove(self, name, force=False, complete_task=False): wt = self.index["worktrees"][name] # ① 运行 git worktree remove self._run_git(["worktree", "remove", wt["path"]]) # ② 如果绑定了任务且需要 complete if complete_task and wt.get("task_id") is not None: task_id = wt["task_id"] self.tasks.update(task_id, status="completed") self.tasks.unbind_worktree(task_id) # ③ 发出事件 self.events.emit("task.completed", { "task": {"id": task_id, "status": "completed"}, "worktree": {"name": name, "status": "removed"} })
💻 英文代码原版
def remove(self, name, force=False, complete_task=False): wt = self.index["worktrees"][name] self._run_git(["worktree", "remove", wt["path"]]) if complete_task and wt.get("task_id") is not None: task_id = wt["task_id"] self.tasks.update(task_id, status="completed") self.tasks.unbind_worktree(task_id) self.events.emit("task.completed", ...)

一个调用,三件事:拆除 worktree + 完成任务 + 发出事件。不是三个独立的步骤(容易遗漏)——一个入口保证三件事原子地发生在同一个调用里。

6

events.jsonl:显式的生命周期日志

s11 的 Agent 在后台做事,Lead 很难知道"现在进度如何?Alice 的 worktree 创建成功了吗?Bob 的任务完成了吗?"——s12 用 events.jsonl 显式记录每个生命周期步骤。

事件类型:覆盖完整生命周期

# .worktrees/events.jsonl — 每一行是一个事件 {"event":"worktree.create.before", "name":"auth-refactor", "ts":1730000000} {"event":"worktree.create.after", "task":{"id":1, "status":"in_progress"}, "ts":1730000001} {"event":"worktree.remove.before", "name":"auth-refactor", "ts":1730001000} {"event":"task.completed", "task":{"id":1, "status":"completed"}, "ts":1730001001} {"event":"worktree.remove.after", "worktree":{"name":"auth-refactor", "status":"removed"}, "ts":1730001001}

事件流的三个用途

① 调试:Agent 行为异常?tail -f .worktrees/events.jsonl 实时看生命周期流转。哪个 step 卡住了,一眼就知道。

② 崩溃恢复:Agent 崩溃了,但 worktree.create.after 事件已经写入——重启后知道"上次创建到一半,需要清理遗留 worktree"。

③ 审计:谁在什么时候创建了哪个 worktree?哪个任务什么时候完成的?events.jsonl 就是 Agent 团队的 audit log。

⭐ 事件类型完整列表:worktree.create.beforeworktree.create.afterworktree.create.failedworktree.remove.beforeworktree.remove.afterworktree.remove.failedworktree.keeptask.completed

7

崩溃恢复 & 代码拆解

崩溃后从磁盘重建现场

Agent 的会话(messages)是易失的——崩溃了就没了。但文件是持久的

.tasks/ 知道"任务 #1 的状态是 in_progress,绑定了 worktree auth-refactor"。

.worktrees/index.json 知道"auth-refactor worktree 在哪个路径、在哪个分支"。

.worktrees/events.jsonl 知道"最后一条事件是 worktree.remove.before 还是 after?"

重启后,不需要 Agent 的上下文记忆——读这三个文件,就能完整重建现场:哪些任务进行中、哪些 worktree 需要清理、哪些分支有未合并的 commit。

会话记忆是易失的;磁盘状态是持久的。这是 s07-s12 一以贯之的哲学。
A

WorktreeManager(~80 行)

create() + bind_worktree() + remove() + keep() + list()。管理 .worktrees/ 目录下的 index.json 和 events.jsonl。

B

Task schema 升级(1 个字段)

task_*.json 新增 "worktree": "" 字段。s07 的任务结构向后兼容——空字符串 = 共享目录(s11 行为),非空 = 隔离 worktree(s12 行为)。

C

Teammate Loop 改动(~6 行)

spawn/claim 时调 WORKTREES.create(name, task_id=id),agent loop 末尾接 keep 或 remove。和 s09/s11 的集成点。

D

events.jsonl 管道(~15 行)

emit() 往 events.jsonl 追加一行 JSON。所有生命周期变更都经过它——可观测、可审计、可恢复。

组件s11s12
协调任务板 (owner/status)任务板 + worktree 显式绑定
执行范围共享目录每个任务独立目录
可恢复性仅任务状态任务状态 + worktree index + events
收尾任务完成任务完成 + 显式 keep/remove
生命周期可见性隐式日志events.jsonl 显式事件流
8

从 s12 带走的四个核心认识

01

双面绑定

控制面管"做什么",执行面管"在哪做"。task_id 是两面的桥梁。bind_worktree 一步同步两侧状态。

02

事件流 = 可观测性

events.jsonl 不复杂——就是追加 JSON 行。但有了它,Agent 的生命周期从"黑盒"变成了"可审计"。

03

隔离代替协调

共享目录 → 需要锁和协议(s10)。隔离目录 → 冲突不发生。对 Agent 系统,隔离的成本远低于协调的成本。

04

磁盘 > 会话

会话记忆易失,磁盘状态持久。崩溃后从 .tasks/ + .worktrees/index.json + events.jsonl 重建现场。不依赖任何内存状态。

9

落地案例:Worktree 隔离在真实系统中的应用

📋 Claude Code 的 EnterWorktree / ExitWorktree

Claude Code 的 EnterWorktreeExitWorktree 工具就是 s12 的生产实现。当你让 Claude Code "在 worktree 里做这个改动",它在 .claude/worktrees/ 下创建隔离的 git worktree,在新目录里完成所有修改,commit,然后通过 ExitWorktree 清理或保留。

整个过程和 s12 完全一致:create → bind → execute with cwd → keep/remove → emit events。你每次用 worktree 功能都是在经历 s12 的完整生命周期。

🔄 GitHub Actions 的 Job Runner

GitHub Actions 的每个 workflow job 跑在独立的 runner 上——checkout 同一份代码的副本,独立修改、测试,最后通过 PR 回传。这和 s12 的"每个任务一个 worktree"是同构的——每个 job = 一个隔离的执行环境 = 一个 task 绑定一个 worktree。

🏗️ Git 本身的分布式哲学

s12 的双面架构(控制面 + 执行面)就是 Git 分布式模型的微缩版。Git 的核心假设是"每个人在本地仓库工作,通过分支和 merge 同步"。s12 只不过把这个模型搬到了单机多 Agent 的场景——每个 worktree 就是一个"本地仓库",task 就是"工作目标"。Git 花了 20 年证明这套模型适合人类团队——s12 证明它同样适合 Agent 团队。

🎯 12 节课程完整图景

模块课程一句话总结
基础循环s01-s04Agent 怎么干活——循环 + 工具 + 外部记忆 + 子 Agent
知识上下文s05-s06怎么按需加载技能 + 怎么压缩释放上下文
任务与团队s07-s09持久化任务图 + 后台执行 + 多 Agent 邮箱通信
隔离与治理s10-s12协作协议 + 自主认领 + Worktree 文件隔离

while True 出发,走过 12 节课,你现在站在一个可治理的多 Agent 系统的最顶层。循环 → 工具 → 记忆 → 委派 → 技能 → 压缩 → 任务图 → 后台 → 团队 → 协议 → 自主 → 隔离。每一层解决一个问题,每一层为下一层打基础。

🎉 12 节课全部完成。
工程不是学完的,是改完的。
去读 Claude Code 源码验证你的理解,用这些模式改造你的 Agent 项目,或设计全新的多 Agent 系统。
动手吧。

s12 学完了"是什么"和"为什么"。但具体怎么搭建一个 Harness?
s13:从零搭建你自己的 LLM Agent 外壳——逐层组装,全部可运行代码。

s13: Build Your Own Harness →