Agent 开发进阶(十八):Worktree 任务隔离,让多个任务互不干扰
本文是「从零构建 Coding Agent」系列的第十八篇,适合想让多个任务并行执行且互不干扰的开发者。
先问一个问题
当你的多个 Agent 并行执行不同任务时,你是否遇到过这样的问题:
- 两个任务同时修改同一个文件,导致冲突
- 一个任务还没做完,另一个任务的修改已经污染了工作目录
- 想单独回看某个任务的改动范围,却很难分清
如果你的答案是肯定的,那么你需要一个 Worktree 任务隔离系统。
并行工作的「目录冲突」问题
到了这一阶段,你的 Agent 已经具备了多种能力:
- 核心循环运行
- 工具使用与分发
- 会话内规划(TodoWrite)
- 子智能体机制(Subagent)
- 技能加载
- 上下文压缩
- 权限系统
- Hook 系统
- Memory 系统
- 系统提示词组装
- 错误恢复
- 任务系统
- 后台任务系统
- 定时调度系统
- Agent 团队系统
- 团队协议系统
- 自治智能体系统
但当多个 Agent 并行执行任务时,在同一个工作目录里会遇到明显问题:
- 文件冲突:两个任务同时修改同一个文件
- 目录污染:一个任务的修改影响另一个任务
- 难以追踪:无法单独查看某个任务的改动范围
- 回滚困难:很难单独回滚某个任务
虽然 s17 的自治系统让 Agent 可以主动认领任务,但如果所有人都在同一个工作目录里改文件,很快就会出现冲突。
所以到了这个阶段,我们需要一个 Worktree 任务隔离系统:
把每个任务放到独立的工作目录里执行,避免互相干扰。
Worktree 任务隔离系统的核心设计:任务与工作目录的绑定
用一个图来表示 Worktree 任务隔离系统的工作流程:
任务被创建
->
队友认领任务
->
系统为任务分配 worktree
->
命令在对应目录里执行
->
任务完成时决定保留还是删除 worktree
关键点只有三个:
- 任务绑定:把任务 ID 和 worktree 明确关联
- 目录隔离:每个任务在独立目录中执行
- 收尾管理:任务完成后决定保留或删除 worktree
几个必须搞懂的概念
Worktree(工作树)
如果你熟悉 git,可以把 worktree 理解成:
同一个仓库的另一个独立检出目录。
如果你还不熟悉 git,也可以先把它理解成:
一条属于某个任务的独立工作车道。
隔离执行(Isolated Execution)
隔离执行就是:
任务 A 在自己的目录里跑,任务 B 在自己的目录里跑,彼此默认不共享未提交改动。
绑定(Binding)
绑定的意思是:
把某个任务 ID 和某个 worktree 记录明确关联起来。
收尾(Closeout)
收尾就是任务完成后,决定 worktree 的最终状态:
- 保留:保留目录,方便后续查看或继续工作
- 删除:删除目录,释放资源
最小实现
1. Worktree Manager
import os
import json
import subprocess
import time
import uuid
from pathlib import Path
class WorktreeManager:
"""Worktree 管理器"""
def __init__(self, worktrees_dir=".worktrees", index_file=".worktrees/index.json"):
self.worktrees_dir = Path(worktrees_dir)
self.worktrees_dir.mkdir(exist_ok=True)
self.index_file = Path(index_file)
self.index_file.parent.mkdir(exist_ok=True)
# 加载索引
self.index = self._load_index()
# 事件日志
self.events_file = self.worktrees_dir / "events.jsonl"
def _load_index(self):
"""加载索引"""
if self.index_file.exists():
try:
return json.loads(self.index_file.read_text(encoding="utf-8"))
except Exception as e:
print(f"加载索引失败: {e}")
return {"worktrees": []}
def _save_index(self):
"""保存索引"""
self.index_file.write_text(
json.dumps(self.index, indent=2, ensure_ascii=False),
encoding="utf-8"
)
def _log_event(self, event):
"""记录事件"""
with open(self.events_file, "a", encoding="utf-8") as f:
f.write(json.dumps(event, ensure_ascii=False) + "\n")
def _run_git(self, args, cwd=None):
"""运行 git 命令"""
try:
result = subprocess.run(
["git"] + args,
cwd=cwd,
capture_output=True,
text=True,
check=True
)
return result.stdout
except subprocess.CalledProcessError as e:
print(f"Git 命令失败: {e}")
raise
def create(self, name, task_id):
"""创建 worktree"""
path = self.worktrees_dir / name
branch = f"wt/{name}"
# 创建 worktree
self._run_git(["worktree", "add", "-b", branch, str(path), "HEAD"])
# 创建记录
record = {
"name": name,
"path": str(path),
"branch": branch,
"task_id": task_id,
"status": "active",
"created_at": time.time(),
"last_entered_at": None,
"last_command_at": None,
"last_command_preview": None,
"closeout": None,
}
self.index["worktrees"].append(record)
self._save_index()
# 记录事件
self._log_event({
"event": "worktree.created",
"name": name,
"task_id": task_id,
"path": str(path),
"ts": time.time(),
})
return record
def get(self, name):
"""获取 worktree"""
for wt in self.index["worktrees"]:
if wt["name"] == name:
return wt
return None
def enter(self, name):
"""进入 worktree"""
worktree = self.get(name)
if not worktree:
raise ValueError(f"Worktree {name} 不存在")
# 更新记录
worktree["last_entered_at"] = time.time()
self._save_index()
# 记录事件
self._log_event({
"event": "worktree.entered",
"name": name,
"task_id": worktree["task_id"],
"ts": time.time(),
})
return worktree["path"]
def run(self, name, command):
"""在 worktree 中执行命令"""
worktree = self.get(name)
if not worktree:
raise ValueError(f"Worktree {name} 不存在")
path = worktree["path"]
# 执行命令
try:
result = subprocess.run(
command,
shell=True,
cwd=path,
capture_output=True,
text=True,
timeout=300
)
# 更新记录
worktree["last_command_at"] = time.time()
worktree["last_command_preview"] = command[:100]
self._save_index()
# 记录事件
self._log_event({
"event": "worktree.command.executed",
"name": name,
"task_id": worktree["task_id"],
"command": command,
"exit_code": result.returncode,
"ts": time.time(),
})
return {
"success": result.returncode == 0,
"stdout": result.stdout,
"stderr": result.stderr,
"exit_code": result.returncode,
}
except subprocess.TimeoutExpired:
return {
"success": False,
"stdout": "",
"stderr": "Command timed out",
"exit_code": -1,
}
def closeout(self, name, action, reason="", complete_task=False):
"""收尾 worktree"""
worktree = self.get(name)
if not worktree:
raise ValueError(f"Worktree {name} 不存在")
# 检查未提交改动
path = worktree["path"]
try:
status = self._run_git(["status", "--porcelain"], cwd=path)
if status.strip():
print(f"警告: Worktree {name} 有未提交的改动")
# 实际应该询问用户是否继续
except:
pass
# 记录收尾动作
closeout_record = {
"action": action,
"reason": reason,
"at": time.time(),
}
worktree["closeout"] = closeout_record
if action == "keep":
worktree["status"] = "kept"
elif action == "remove":
worktree["status"] = "removed"
# 删除 worktree
self._run_git(["worktree", "remove", name])
self._save_index()
# 记录事件
self._log_event({
"event": f"worktree.closeout.{action}",
"name": name,
"task_id": worktree["task_id"],
"reason": reason,
"complete_task": complete_task,
"ts": time.time(),
})
return {
"action": action,
"name": name,
"task_id": worktree["task_id"],
"complete_task": complete_task,
}
def list(self):
"""列出所有 worktree"""
if not self.index["worktrees"]:
return "暂无 worktree"
lines = ["# Worktrees\n"]
for wt in self.index["worktrees"]:
lines.append(f"- **{wt['name']}** ({wt['status']})")
lines.append(f" 路径: {wt['path']}")
lines.append(f" 分支: {wt['branch']}")
lines.append(f" 任务: #{wt['task_id']}")
if wt.get('last_command_preview'):
lines.append(f" 最后命令: {wt['last_command_preview']}")
return "\n".join(lines)
2. 增强的任务管理器
class WorktreeTaskManager:
"""支持 worktree 的任务管理器"""
def __init__(self, tasks_dir=".tasks"):
self.tasks_dir = Path(tasks_dir)
self.tasks_dir.mkdir(exist_ok=True)
self.tasks = {}
self._load_all_tasks()
def _load_all_tasks(self):
"""加载所有任务"""
for task_file in self.tasks_dir.glob("task_*.json"):
try:
task = json.loads(task_file.read_text(encoding="utf-8"))
self.tasks[task["id"]] = task
except Exception as e:
print(f"加载任务失败 {task_file}: {e}")
def _save_task(self, task):
"""保存任务"""
task_file = self.tasks_dir / f"task_{task['id']}.json"
task_file.write_text(
json.dumps(task, indent=2, ensure_ascii=False),
encoding="utf-8"
)
def create(self, subject, description=""):
"""创建任务"""
task = {
"id": len(self.tasks) + 1,
"subject": subject,
"description": description,
"status": "pending",
"blockedBy": [],
"blocks": [],
"owner": "",
"worktree": None,
"worktree_state": "unbound",
"last_worktree": None,
"closeout": None,
}
self.tasks[task["id"]] = task
self._save_task(task)
return task
def bind_worktree(self, task_id, worktree_name):
"""绑定 worktree"""
task = self.tasks.get(task_id)
if not task:
return None
task["worktree"] = worktree_name
task["last_worktree"] = worktree_name
task["worktree_state"] = "active"
if task["status"] == "pending":
task["status"] = "in_progress"
self._save_task(task)
return task
def update_worktree_state(self, task_id, worktree_state, closeout=None):
"""更新 worktree 状态"""
task = self.tasks.get(task_id)
if not task:
return None
task["worktree_state"] = worktree_state
if closeout:
task["closeout"] = closeout
self._save_task(task)
return task
3. 集成到自治系统
class WorktreeAutonomousTeammate:
"""支持 worktree 的自治队友"""
def __init__(self, name, role, task_manager, worktree_manager, prompt):
self.name = name
self.role = role
self.task_manager = task_manager
self.worktree_manager = worktree_manager
self.prompt = prompt
self.messages = [{
"role": "system",
"content": f"你是 {name},一个 {role}。请专注于你的职责,完成任务后等待新的指示。"
}, {
"role": "user",
"content": prompt
}]
self.running = True
self.idle_count = 0
self.max_idle_count = 10
self.poll_interval = 5
def claim_and_execute(self, task):
"""认领并执行任务"""
task_id = task["id"]
# 创建 worktree
worktree_name = f"task-{task_id}-{self.name}"
worktree = self.worktree_manager.create(worktree_name, task_id)
# 绑定 worktree
self.task_manager.bind_worktree(task_id, worktree_name)
# 进入 worktree
worktree_path = self.worktree_manager.enter(worktree_name)
print(f"{self.name} 在 {worktree_path} 中执行任务 #{task_id}")
# 执行任务(简化版本)
# 实际应该根据任务内容执行相应命令
result = self.worktree_manager.run(worktree_name, "echo 'Task executed'")
if result["success"]:
# 任务完成,决定收尾
closeout_result = self.worktree_manager.closeout(
worktree_name,
action="keep",
reason="Task completed, keeping for review",
complete_task=True
)
# 更新任务状态
self.task_manager.update_worktree_state(
task_id,
worktree_state="kept",
closeout=closeout_result
)
print(f"{self.name} 完成任务 #{task_id},worktree 已保留")
else:
print(f"{self.name} 执行任务 #{task_id} 失败: {result['stderr']}")
def run(self):
"""主循环"""
print(f"队友 {self.name} ({self.role}) 已启动")
while self.running:
# 扫描可认领任务
claimable_tasks = self.task_manager.get_claimable_tasks(self.role)
if claimable_tasks:
task = claimable_tasks[0]
print(f"{self.name} 认领任务: #{task['id']} {task['subject']}")
# 认领并执行任务
self.claim_and_execute(task)
self.idle_count = 0
else:
self.idle_count += 1
print(f"{self.name} 空闲 ({self.idle_count}/{self.max_idle_count})")
if self.idle_count >= self.max_idle_count:
print(f"{self.name} 长时间空闲,准备退出")
break
time.sleep(self.poll_interval)
print(f"队友 {self.name} ({self.role}) 已退出")
核心功能说明
1. 创建 Worktree
为任务创建独立工作目录:
worktree = worktree_manager.create("auth-refactor", task_id=12)
创建过程包括:
- 使用 git worktree 创建独立检出
- 在索引中注册 worktree 记录
- 记录创建事件
2. 绑定任务和 Worktree
将任务与 worktree 关联:
task_manager.bind_worktree(task_id=12, worktree_name="auth-refactor")
绑定后,任务记录会包含:
worktree:当前绑定的 worktree 名称worktree_state:绑定状态last_worktree:最后使用的 worktree
3. 在 Worktree 中执行命令
进入 worktree 并执行命令:
worktree_path = worktree_manager.enter("auth-refactor")
result = worktree_manager.run("auth-refactor", "pytest tests/auth -q")
关键点:
- 命令在 worktree 的目录中执行
- 不同 worktree 中的命令互不影响
- 记录命令执行历史
4. Worktree 收尾
任务完成后决定 worktree 的最终状态:
worktree_manager.closeout(
name="auth-refactor",
action="keep", # 或 "remove"
reason="Task completed, keeping for review",
complete_task=True
)
收尾动作:
- keep:保留 worktree,方便后续查看
- remove:删除 worktree,释放资源
Worktree vs 任务状态
| 状态 | 任务状态 | Worktree 状态 | 说明 |
|---|---|---|---|
| 任务进行中 | in_progress | active | 任务正在执行,worktree 正在使用 |
| 任务完成 | completed | kept | 任务完成,worktree 保留供查看 |
| 任务完成 | completed | removed | 任务完成,worktree 已删除 |
| 任务未开始 | pending | unbound | 任务未认领,没有关联的 worktree |
关键点:任务状态和 worktree 状态是独立的,不能混为一谈。
新手最容易犯的 7 个错
1. 有 worktree 注册表,但任务记录里没有 worktree
# ❌ 错误
# 只创建了 worktree,没有更新任务记录
worktree_manager.create("auth-refactor", task_id=12)
# ✅ 正确
# 同时更新任务记录
worktree_manager.create("auth-refactor", task_id=12)
task_manager.bind_worktree(12, "auth-refactor")
2. 有任务 ID,但命令仍然在主目录执行
# ❌ 错误
# 没有切换到 worktree 目录
subprocess.run("pytest tests/auth", shell=True)
# ✅ 正确
# 在 worktree 目录中执行
worktree_path = worktree_manager.enter("auth-refactor")
subprocess.run("pytest tests/auth", shell=True, cwd=worktree_path)
3. 只会删除 worktree,不会解释 closeout 的含义
# ❌ 错误
# 只知道删除目录
subprocess.run(["git", "worktree", "remove", "auth-refactor"])
# ✅ 正确
# 明确收尾动作和原因
worktree_manager.closeout(
name="auth-refactor",
action="keep",
reason="Need follow-up review",
complete_task=False
)
4. 删除 worktree 前不看未提交改动
# ❌ 错误
# 直接删除,可能丢失未提交的改动
subprocess.run(["git", "worktree", "remove", "auth-refactor"])
# ✅ 正确
# 删除前检查未提交改动
status = subprocess.run(["git", "status", "--porcelain"], cwd=path)
if status.stdout.strip():
print("警告: 有未提交的改动")
# 询问用户是否继续
5. 没有 worktree_state / closeout 这类显式收尾状态
# ❌ 错误
# 只记录 worktree 是否存在
task["worktree"] = "auth-refactor"
# ✅ 正确
# 记录详细的状态信息
task["worktree"] = "auth-refactor"
task["worktree_state"] = "kept"
task["closeout"] = {
"action": "keep",
"reason": "Task completed",
"at": time.time()
}
6. 把 worktree 当成长期垃圾堆
# ❌ 错误
# 从不清理 worktree
def create_worktree(name):
# 只创建,不清理
pass
# ✅ 正确
# 定期清理不需要的 worktree
def cleanup_old_worktrees():
# 删除超过一定时间未使用的 worktree
pass
7. 没有事件日志
# ❌ 错误
# 没有记录事件,难以排查问题
def create_worktree(name):
subprocess.run(["git", "worktree", "add", name])
# 没有记录
# ✅ 正确
# 记录所有重要事件
def create_worktree(name):
subprocess.run(["git", "worktree", "add", name])
log_event({
"event": "worktree.created",
"name": name,
"ts": time.time()
})
为什么这很重要
因为一个真正可靠的并行系统,需要清晰的隔离机制。
Worktree 任务隔离系统让你能够:
- 避免冲突:每个任务在独立目录中执行,避免文件冲突
- 清晰追踪:可以单独查看每个任务的改动范围
- 安全回滚:可以单独回滚某个任务,不影响其他任务
- 并行执行:多个任务可以同时进行,互不干扰
- 资源管理:可以灵活地保留或删除 worktree,管理资源使用
推荐的实现步骤
- 第一步:实现 WorktreeManager 类,管理 worktree 的创建和删除
- 第二步:实现 worktree 的进入和命令执行
- 第三步:实现 worktree 的收尾机制(保留或删除)
- 第四步:增强任务管理器,支持 worktree 绑定
- 第五步:集成到自治系统,让队友在 worktree 中执行任务
- 第六步:实现事件日志,记录所有重要操作
- 第七步:添加安全检查,避免意外删除未提交的改动
Worktree 任务隔离与后续章节的关系
- s18 Worktree:解决任务在哪里执行且互不干扰的问题
- s19 MCP 插件:会利用 worktree 来扩展系统能力
所以 Worktree 任务隔离是构建高级并行系统的基础组件。
下一章预告
有了 Worktree 任务隔离系统,你的多个 Agent 已经可以在独立的工作目录中并行执行任务。下一章我们将探讨 MCP 插件系统,让 Agent 能够通过插件扩展能力,实现更强的灵活性。
一句话总结:任务系统管「做什么」,worktree 系统管「在哪做且互不干扰」。
如果觉得有帮助,欢迎关注,我会持续更新「从零构建 Coding Agent」系列文章。