1. 从"能写代码"到"会派活":这次改造到底改了什么
Claude Code 这个工具,很多人第一反应是"终端里的 AI 编程助手"。你给它一句话,它读文件、改代码、跑测试,干完活给你汇报。这套模式在单任务场景下非常好用,但一旦任务变多、依赖变复杂,问题就来了——它本质上还是一个"你问我答"的被动执行者,不会自己安排先干什么后干什么,更不会把一个大目标拆成若干子任务分头推进。
这次社区里讨论度很高的一个改造方向,就是把它从"执行者"升级成"调度者"。具体来说,是在 Claude Code 之上叠了一层任务调度逻辑:接收一个高层目标,自动拆解成子任务,判断哪些能并行、哪些必须串行,然后分派给不同的执行单元去跑,最后汇总结果。关键词里的 Agent、Supervisor、worktree 三个词,基本就把这套设计的骨架说清楚了——Agent 是干活的单元,Supervisor 是管人的角色,worktree 是隔离工作空间的机制。
我之所以觉得"设计比功能更值得看",是因为功能层面无非是"多跑几个任务",但设计层面它回答了一个更本质的问题:当一个 AI 编程工具从单线程走向多线程协作时,怎么保证不互相踩脚、不重复劳动、不把代码库搞乱。这套问题的答案,比"支持并发"这四个字有价值得多。
这篇文章适合两类人看。一类是已经在用 Claude Code、想把它用得更"重"的开发者,比如同时维护多个模块、想批量处理重构任务的人;另一类是在做 Agent 开发、关心任务编排和上下文隔离的人,哪怕你不用 Claude Code,这套 Supervisor + worktree 的思路也能直接搬到自己的 Agent 项目里。下面我会从设计思路、核心机制、实操落地到踩坑排查,一层层拆开讲。
2. 整体设计思路:为什么是 Supervisor 而不是"一个大 Agent 干到底"
2.1 单 Agent 模式的三个天花板
先说清楚为什么要改。单 Agent 模式,就是所有事情都由一个对话上下文完成。它有三个绕不过去的天花板。
第一个是上下文窗口的物理限制。一个任务如果涉及十几个文件、几万行代码,光是读进来的内容就把窗口占满了,模型还没开始推理就已经"内存不足"。你可能会说可以压缩、可以摘要,但摘要本身会丢信息,丢的往往就是关键细节。
第二个是串行执行的效率问题。单 Agent 一次只能推进一件事,A 模块改完才能改 B 模块,哪怕这两个模块八竿子打不着。在真实项目里,很多任务是天然可并行的,硬串起来就是浪费时间。
第三个是错误传播的污染问题。一个 Agent 在长对话里如果早期理解错了需求,后面所有输出都会带着这个错误往下走,而且很难自我纠正,因为它已经把错误当成了既定事实。
2.2 Supervisor 模式的核心分工
Supervisor 模式本质上是把"决策"和"执行"拆开。Supervisor 不干具体的活,它只做三件事:拆解目标、分派任务、验收结果。真正读代码、写代码、跑测试的是下面一个个独立的 Agent 实例。
这么拆的好处很直接。Supervisor 的上下文里只需要装"任务清单和状态",不需要装具体代码,窗口压力小得多。每个执行 Agent 只关心自己那一个子任务,上下文干净,不容易被无关信息干扰。而且因为执行单元是独立的,一个挂了不影响其他,重试成本也低。
打个比方,这就像装修。单 Agent 模式是一个师傅从头干到尾,水电、木工、油漆全包,累且慢。Supervisor 模式是包工头接活,然后叫水电工、木工、油漆工分头干,包工头只负责排期和验收。哪个工种出问题,单独返工就行。
2.3 worktree 为什么是这套设计的关键拼图
如果只是分派任务,那多个 Agent 同时改同一个代码库,冲突几乎是必然的。你改utils.py第 30 行,我也改第 30 行,最后合并的时候就是一场灾难。
git worktree 解决的正是这个问题。它允许你在同一个仓库下开出多个独立的工作目录,每个目录对应一个分支,彼此的文件系统是隔离的。Agent A 在 worktree-1 里改,Agent B 在 worktree-2 里改,物理上就不在一个目录,根本不会互相覆盖。等各自改完,再通过正常的合并流程把分支合回去。
提示:worktree 不是复制仓库,它共享同一个
.git对象库,所以创建成本极低,几乎瞬间完成,磁盘占用也远小于完整 clone。这是它比"复制一份代码"更优的根本原因。
把这三块拼起来,整套设计的逻辑就通了:Supervisor 负责"想",Agent 负责"做",worktree 负责"隔"。想清楚这个分工,后面所有的实操细节都是围绕这三件事展开的。
3. 核心机制拆解:任务拆解、分派与隔离怎么落地
3.1 任务拆解:从一句话目标到可执行清单
Supervisor 拿到的输入通常是一句人话,比如"把项目里的日志系统从 print 换成 logging 模块"。它要做的第一件事是把这个目标拆成可独立执行的子任务。
拆解的核心原则是子任务之间尽量低耦合。判断标准很简单:如果两个子任务会改到同一批文件,那它们就不该并行,要么合并成一个任务,要么强制串行。日志改造这个例子里,可以拆成"扫描所有 print 调用点"、"按模块分组"、"逐模块替换"、"跑测试验证"四步。前三步里,按模块分组后的替换是可以并行的,因为不同模块的文件不重叠。
拆解粒度也有讲究。太粗,一个子任务还是太大,起不到隔离作用;太细,任务数量爆炸,Supervisor 的调度开销反而成了瓶颈。我的经验是,单个子任务的预期执行时间控制在几分钟量级比较合适,对应大概改动 3 到 10 个文件。这个粒度下,Agent 的上下文够用,失败了重试也不心疼。
3.2 分派策略:并行、串行与依赖判断
拆完之后是分派。这里要维护一张依赖图,明确哪些任务有前置条件。日志改造里,"替换"依赖"扫描"的结果,"验证"依赖所有"替换"完成,这就是一条清晰的依赖链。
分派策略可以归纳成三条规则:
- 无依赖的任务并行跑,各自开一个 worktree,互不干扰。
- 有依赖的任务串行跑,前一个的产出作为后一个的输入。
- 有文件冲突的任务强制串行,哪怕逻辑上没依赖,只要改同一批文件就得排队。
第三条最容易被忽略。很多人只看逻辑依赖,不看文件依赖,结果两个 Agent 同时改一个文件,合并时冲突一堆。判断文件冲突可以在拆解阶段就做,让 Supervisor 先扫一遍每个子任务涉及的文件列表,有交集就标记为互斥。
3.3 worktree 隔离:创建、命名与回收
worktree 的创建命令很直接:
git worktree add ../worktrees/task-001 -b task-001-branch这行命令在仓库上一级目录建了个worktrees/task-001目录,检出到新分支task-001-branch。Agent 就在这个目录里干活,改的文件、跑的命令都局限在这里。
命名建议带上任务 ID 和简短描述,比如task-001-logging-scan,这样一眼能看出这个 worktree 是干嘛的,排查问题时不用去翻日志。任务完成后,worktree 要及时回收:
git worktree remove ../worktrees/task-001 git branch -d task-001-branch注意:worktree 不回收会越积越多,磁盘虽然占用不大,但
git worktree list会越来越长,管理起来很烦。建议在任务状态机里把"回收 worktree"作为完成态的一个必经步骤,别靠人记。
3.4 结果汇总:合并冲突的处理原则
所有子任务跑完,Supervisor 要把各分支合回主干。这里的原则是能自动合就自动合,合不了就交给人。因为 worktree 隔离做得好,大部分情况下各分支改的文件不重叠,合并是干净的。
一旦出现冲突,不要指望 Agent 自己瞎合。正确做法是把冲突文件、冲突片段、两个分支各自的意图一起抛给一个专门的"合并 Agent",让它基于意图判断怎么合,合完再跑一遍测试。如果测试挂了,直接回滚这个合并,标记为需要人工介入。这条兜底逻辑很重要,它保证了自动化流程不会把代码库搞坏。
4. 实操落地:从零搭一套可跑的任务调度流程
4.1 环境准备与基础配置
先把基础环境理清楚。Claude Code 的安装方式这里不展开,假设你已经能在终端里正常调用它。真正要额外准备的是三样东西:一个支持 worktree 的 git 仓库、一个任务状态的存储位置、一个调度脚本的入口。
任务状态我建议就用一个 JSON 文件,简单直接,别一上来就上数据库。结构大概是这样:
{ "goal": "把日志系统从 print 换成 logging", "tasks": [ {"id": "task-001", "desc": "扫描 print 调用点", "deps": [], "status": "done", "worktree": null}, {"id": "task-002", "desc": "替换 module-a", "deps": ["task-001"], "status": "running", "worktree": "task-002-replace-a"}, {"id": "task-003", "desc": "替换 module-b", "deps": ["task-001"], "status": "pending", "worktree": null} ] }这个文件就是 Supervisor 的"大脑"。它每次调度前读一遍,看哪些任务依赖已满足、哪些在跑、哪些该回收,然后决定下一步动作。用文件而不是内存,好处是进程崩了状态还在,重启能接着跑。
4.2 调度循环的伪代码实现
调度循环的逻辑不复杂,核心就是一个 while 循环加状态判断:
while not all_done(tasks): ready = [t for t in tasks if t.status == "pending" and deps_satisfied(t, tasks)] for t in ready: if not conflicts_with_running(t, tasks): wt = create_worktree(t.id) t.worktree = wt t.status = "running" spawn_agent(t, wt) # 异步启动执行 Agent for t in tasks: if t.status == "running" and agent_finished(t): if verify(t): t.status = "done" merge_branch(t) remove_worktree(t) else: t.status = "failed" save_state(tasks) sleep(5)这段代码里有两个关键点。conflicts_with_running是防文件冲突的闸门,它检查待启动任务涉及的文件是否和正在跑的任务有交集。verify是验收环节,通常是跑一遍相关测试,测试过了才算 done。没有验收的调度就是耍流氓,Agent 说"我改完了"不等于改对了。
4.3 单个执行 Agent 的提示词设计
执行 Agent 的提示词决定了它干活的质量。核心是给它三样东西:明确的任务边界、可用的工具说明、验收标准。
一个可用的模板长这样:
你是一个代码修改 Agent,工作目录是 {worktree_path}。 你的任务:{task_desc} 约束: 1. 只修改与任务直接相关的文件,不要顺手重构无关代码。 2. 改完必须运行 {test_command} 并确保通过。 3. 如果遇到无法解决的问题,输出 FAILED 加原因,不要强行改。 完成后输出修改的文件列表和测试结果。第 1 条约束特别重要。Agent 有个通病是"手痒",看到不顺眼的代码就想一起改了,这在并行场景下是灾难,因为它会碰到别的任务的文件。把边界写死,能省掉大量合并冲突。
4.4 一个完整的实操案例记录
拿一个真实的小项目举例。目标是把散落在各处的print换成logging。项目有 5 个模块,共 40 多处 print。
第一步,Supervisor 扫描,产出任务清单:task-001 扫描(串行),task-002 到 task-006 分别替换 5 个模块(并行),task-007 跑全量测试(串行,依赖 002-006)。
第二步,task-001 跑完,输出每个模块的 print 位置清单。这一步很快,几十秒。
第三步,002 到 006 同时启动,各开一个 worktree。实测下来,5 个 Agent 并行跑,总耗时约等于单个模块的耗时,因为它们是真正同时进行的。如果串行跑,时间要乘以 5。
第四步,5 个分支依次合并。因为每个 Agent 只改自己模块的文件,合并零冲突。这一步验证了前面"文件不重叠"的拆解原则是对的。
第五步,task-007 在合并后的主干上跑全量测试,通过,收工。
整个流程从扫描到验证完成,比单 Agent 串行快了大概 4 倍。这个提速不是靠模型变快,纯粹是靠并行和隔离。
5. 常见问题与排查技巧实录
5.1 合并冲突频发怎么办
冲突频发,九成是拆解阶段没做好文件隔离。排查思路是回头看任务清单,把每个任务涉及的文件列出来,找交集。有交集的任务要么合并,要么强制串行。
还有一种隐蔽情况:两个任务逻辑上不冲突,但都改了同一个配置文件,比如都往requirements.txt里加依赖。这种要在拆解时识别出来,把配置改动集中到一个任务里做,其他任务不许碰配置文件。
5.2 Agent 卡死或超时怎么处理
Agent 卡死通常有两个原因:一是任务太大,上下文塞爆了;二是遇到了需要人工决策的模糊点,它在那儿反复纠结。
处理办法是给每个任务设超时,比如 10 分钟。超时后强制终止,标记为 failed,然后看日志判断原因。如果是任务太大,就拆得更细;如果是模糊点,就在提示词里补充决策规则,或者干脆把这个任务标成需要人工介入。
提示:超时时间别设太短。Agent 跑测试、读大文件都可能花几分钟,设太短会误杀正常任务。我一般设成预期耗时的 3 倍。
5.3 worktree 残留与分支混乱
跑一段时间后git worktree list一长串,分支也一堆,这是回收没做好。解决办法是在状态机里强制回收,任务一旦进入 done 或 failed 终态,立刻执行 remove 和 branch -d。
如果已经积了一堆,手动清理:
git worktree list | grep worktrees | awk '{print $1}' | xargs -I {} git worktree remove {} git branch | grep 'task-' | xargs git branch -d5.4 常见问题速查表
| 现象 | 可能原因 | 排查方向 | 解决动作 |
|---|---|---|---|
| 合并冲突多 | 文件隔离没做好 | 对比任务文件列表 | 合并任务或强制串行 |
| Agent 超时 | 任务过大或决策模糊 | 看 Agent 日志尾部 | 拆细任务或补决策规则 |
| worktree 残留 | 回收步骤缺失 | git worktree list | 终态强制回收 |
| 测试通过但线上出问题 | 验收标准太弱 | 检查测试覆盖 | 补充针对性测试 |
| 并行没提速 | 任务实际有隐藏依赖 | 看任务时间线 | 重新梳理依赖图 |
5.5 几条踩坑心得
第一条,别一上来就追求全自动。先让 Supervisor 拆解、人工确认任务清单,跑顺了再放开自动拆解。自动拆解翻车的成本比手动确认高得多。
第二条,验收标准要写死在任务里。我见过太多"Agent 说改完了但根本没改对"的情况,根子就是没有明确的验收命令。每个任务都必须绑定一个可执行的验证步骤。
第三条,并行度别拉太满。同时跑 20 个 Agent,机器扛不住,API 也可能限流。实测 4 到 8 个并行是比较舒服的区间,再往上收益递减,管理成本反而上升。
第四条,保留每个任务的完整日志。出问题时,日志是唯一的线索。worktree 可以删,日志别删,按任务 ID 归档,排查历史问题时能救命。
这套 Supervisor + worktree 的设计,真正值钱的地方不在于"能并行",而在于它用一套清晰的隔离和验收机制,把并行的副作用控制住了。功能会过时,设计思路不会。你把这套思路吃透,哪怕换个工具、换个模型,照样能搭出一套靠谱的任务调度流程。我自己在几个项目里复用下来,最深的体会就是:调度系统的上限,取决于你对任务边界的理解有多清楚,工具只是把这个理解落地而已。