我把十个任务拆给五个 agent 并行跑
五个 AI 队友协同工作
Mio 是我在做的一个 AI 伴侣 Telegram bot,里面有五个性格不一样的 AI 角色,每个角色都有自己的人格配置文件、记忆系统和对话风格。功能已经跑起来了,但还有三个大模块一直没做,一个是多模态输入(语音、图片、视频),一个是增强版 onboarding(角色预览、自定义故事、收集用户画像),还有一个是自拍生成。
这三个模块加起来活不少。要做数据库迁移,要写新的 media 处理模块和 Telegram 文件下载,五个角色的配置要更新,onboarding 流程要重写,system prompt 要改,还要搭 selfie 生成管道,前后横跨三层架构、十几个文件。
活这么多,直接把 plan 扔给一个 agent 串行干是不行的,十个任务干下来,context window 肯定撑不住。自己花半天把任务拆好、一个一个写,也挺费劲。所以这次我用了 claude code 的 agent team。
agent team 是什么
简单说就是一个 lead agent 管协调,好几个 teammate agent 同时干活。每个 teammate 都是一个独立的 claude 实例,有自己的 context window,能读写文件,也能给 lead 发消息汇报进度。
它主要靠这几样东西。TaskCreate / TaskUpdate 用来建任务、设依赖关系、跟踪状态。TeamCreate 用来把 teammate agent 拉起来、分任务。SendMessage 是 lead 和 teammate 之间传消息的通道。另外还有一条文件所有权的规矩,每个 teammate 只改分给自己的文件,别人的不碰。
这跟把大任务拆小了再挨个跑不一样。五个 agent 是真的同时在写代码,各自 commit,lead 在中间管依赖、排先后。
十个任务,三批执行
我给 claude code 的是一份完整的实现计划,里面有三个 step、九个子步骤,还有一张标了依赖关系的实现顺序表。它先花两分钟把 codebase 摸了一遍,然后把整个计划拆成了十个任务:
| # | 任务 | 依赖 |
|---|---|---|
| 1 | 数据库迁移(aboutUser, customStory 字段) | — |
| 2 | Media 处理模块(transcribe, vision, process) | — |
| 3 | Telegram 文件下载工具 | — |
| 4 | 接入 media 管道到 server | 2, 3 |
| 5 | 角色摘要 + 自定义故事 + UI | — |
| 6 | Onboarding 增强(故事流、用户画像采集) | 1 |
| 7 | aboutUser/customStory 注入 system prompt | 6 |
| 8 | /reonboard 支持切换角色 | 5, 6 |
| 9 | Selfie 生成模块 | — |
| 10 | Selfie 触发接入 | 9 |
这张表要看的是依赖那一列。1、2、3、5、9 互相不依赖,可以同时开工。4 要等 2 和 3 都做完,6 要等 1,7 要等 6,8 要等 5 和 6,10 要等 9。这就是个典型的 DAG(有向无环图)。claude code 自己看出来了,这十个任务可以分三批并行跑。
十个有依赖关系的任务被识别成一张 DAG,按依赖解除的先后分三批并行执行,每批投入的 agent 数量递减
五个 agent 同时开工
第一波直接五个 agent 一起启动:
5 agents launched
├─ @schema-migrator → #1 DB migration
├─ @media-builder → #2 Media module
├─ @file-downloader → #3 Telegram file download
├─ @preset-designer → #5 Preset summaries + UI
└─ @selfie-builder → #9 Selfie module
每个 agent 拿到的任务描述里都写清楚了,要新建或者改哪些文件,参考哪些现有代码,具体按什么规格实现。而且每个 agent 只能改指定的文件,schema-migrator 只碰 schema.ts,media-builder 只碰 media/ 目录下的文件。几个 agent 同时写代码还不打架,主要就靠这条文件所有权。lead 的面板会实时更新:
并行时每个 agent 只被允许编辑分给自己的文件,虚线隔开各自领地,伸手去碰别人文件会被拦下——文件所有权是防冲突的核心机制
┌─────────────────┬──────────────────────────────────────┬─────────┐
│ Agent │ Task │ Status │
├─────────────────┼──────────────────────────────────────┼─────────┤
│ schema-migrator │ #1 - DB migration │ Running │
│ media-builder │ #2 - Media module │ Running │
│ file-downloader │ #3 - Telegram file download │ Running │
│ preset-designer │ #5 - Preset summaries + UI │ Running │
│ selfie-builder │ #9 - Selfie module │ Running │
└─────────────────┴──────────────────────────────────────┴─────────┘
lead 到底在干嘛
跑起来以后我才看明白 lead 到底在干嘛。第一个干完的是 file-downloader,它的任务简单,一个文件就搞定了。接着 schema-migrator 也干完了。lead 马上反应过来,#1 做完了,#6(onboarding 增强)就不用再等了,于是立刻拉起第六个 agent:
● Task #1 done. That unblocks #6. Spawning onboarding-enhancer now.
这中间我什么都不用管。lead 一直盯着依赖图,每完成一个节点,就看看哪些卡住的任务现在能开始了。后面都是这么推的,selfie-builder 做完 #9,lead 立刻让 selfie-wirer 去做 #10。media-builder 做完 #2,#3 之前也已经做完了,lead 就让 media-wirer 去做 #4。
整个过程一共跑了三波。第一波是五个 agent 并行跑 #1、#2、#3、#5、#9。依赖解开以后是第二波,三个 agent 跑 #4、#6、#10。第三波是两个 agent 跑 #7 和 #8。
并行的代价:噪声
并行也有代价。这个项目配了 git hooks,只要 agent 闲下来、工作目录里又有没提交的文件,hook 就会报警。麻烦的是,五个 agent 同时在写代码,每个 agent 只提交自己的文件,所以工作目录里永远有别人还没提交的文件。结果 hook 就冲着已经干完活的 agent 一遍遍报警:
@file-downloader❯ Hook blocks me — uncommitted files belong to other teammates
@schema-migrator❯ Idle — uncommitted files belong to other teammates
lead 认定这是误报。它没让 agent 去提交别人的文件,也没把 hook 关掉。它看出来这是并行干活躲不掉的代价,就不去管它,接着等还在干活的 agent 干完。
服务器掉线,teammate 自己把活干完了
中间还出了件挺有意思的事。OAuth token 过期了,服务器断了连接。
❯ continue on this task, server was down
⎿ API Error: 401 — OAuth token has expired
我重新登录以后,lead 第一件事就是跑 git status 和 git log。工作目录是干净的,说明服务器掉线那段时间,最后两个 agent(prompt-wirer 和 reonboard-builder)自己把活干完,也提交了。
agent team 这一点很有意思,teammate 是自己独立跑的,干活不靠 lead 来轮询或者确认。所以 lead 掉线了,teammate 接着干,等 lead 回来,活已经干完了。
最终整合
十个任务全做完以后,lead 开始收尾。它先跑 pnpm typecheck,发现两个 TS18048 错误(ctx.callbackQuery 可能是 undefined),直接修掉了。再跑 pnpm test,确认没有新挂的测试。然后把代码过了一遍,看新文件是不是都在、导出对不对。最后又拉起两个 subagent,同时去更新文档和 TODO。最终的结果是这样的:
┌─────┬──────────────────────────────────────┬────────────────────┐
│ # │ Task │ Commits │
├─────┼──────────────────────────────────────┼────────────────────┤
│ 1 │ Schema migration │ 10aa550 │
│ 2 │ Media module │ 8aec253, b39551b │
│ 3 │ Telegram file download │ f243f20 │
│ 4 │ Wire media pipeline │ 620829d │
│ 5 │ Preset summaries + backstories │ 3733f3b, e8903da │
│ 6 │ Onboarding enhancements │ bd148cc, 90da519 │
│ 7 │ System prompt wiring │ 0f94c71 │
│ 8 │ Reonboard with preset switch │ 3d15317, af668b4 │
│ 9 │ Selfie module │ b749684 │
│ 10 │ Selfie trigger wiring │ 387c378, b02f291 │
└─────┴──────────────────────────────────────┴────────────────────┘
整个改动一个 session 就干完了。每个任务都有自己的 commit,翻 git log 能看到谁干的哪一块。
什么时候该用 team,什么时候不该
用了几次以后,我现在大概是这么判断的。这几种情况适合用 team:有三个以上能并行的独立子任务,任务之间的依赖能理成一张清楚的 DAG;改动涉及三个以上不同的目录或者模块;一个 agent 的 context window 装不下全部任务。
不适合的也有几种。改动就集中在一两个文件,直接改就行。任务完全串行、没什么能并行的,用 subagent 就够了。还有得把 context 吃得很透的活,比如仿真调试那种反复迭代的调试,也不适合,因为 team 里每个 agent 的 context 是隔开的,调试状态没法共享。
还有一个感受,context 隔离可能比速度还值钱一些。每个 agent 只要弄懂自己负责的那片代码,不用把整个改动面都读进 context。lead 只要弄懂依赖关系和最后的接口,每个文件具体怎么实现,它不用管。
回头看,lead 干的活比光派任务复杂不少。它要维护依赖图,一个节点做完,就看下游哪些能开始了。干完活的 agent 关掉,要新 agent 的时候再拉起来,不能让干完活的人干等着。它还得分清哪些是真问题,哪些只是并行干活躲不掉的代价。五个 agent 同时干活,工作区里有别人没提交的文件,hook 报警了也不见得就是出事了。服务器掉线的时候 lead 自己也会断,但它回来以后靠 git 状态把进度重新拼了出来。等所有 teammate 跑完,它再统一跑 typecheck 和 test,有问题当场修。
我之前在别写代码了,学管 AI 吧里写过,人正在变成 manager。这次实际就是这么分工的,计划和验收标准是我出的,中间的协调全是 lead 在做,代码是 teammate 写的。具体代码我确实一行都没看,就最后看了一眼 typecheck 的结果。反正下次再有这种改动面大的活,我应该还是会用 team 去跑,到时候再看看它碰上更乱的依赖图还顶不顶得住。
三层协作结构:我在最上层出计划和验收标准,Lead 在中间协调依赖,五个 teammate 在底层写代码,人一行代码都没看
