AI Agent 研发工作流的设计与实现
最近用 Agent 处理需求时,我慢慢养成了一套习惯。
实际使用时,我不会一上来就让 Agent 改代码,而是先让它读原始需求、翻相关代码,再整理一份实现方案。方案出来后,我会开一个新会话,交给另一个 Agent 做 Review。确认没什么明显遗漏,才进入开发。
这样做确实比“把需求扔给一个 Agent,然后等它交作业”稳一些。但重复几次以后,我发现 Agent 在干活,我也没闲着。
我得选择下一个 Agent,把前面的结果复制过去,补上缺少的上下文,再告诉它现在要做什么。Review 提出问题后,我还要把意见送回原来的会话。一项任务来回几轮,剪贴板里全是 Prompt 和 Markdown。
有一次我在几个窗口之间切来切去,突然觉得有点好笑:Agent 没有取代我的工作,我只是成了它们之间的消息队列。
为什么我会用两个 Agent
最开始拆成两个 Agent,并不是为了追求“多 Agent 架构”。我只是发现,让同一个 Agent 检查自己的方案,效果经常不好。
第一轮一旦定下方向,即使再提醒它“仔细检查”,它也很容易顺着原来的思路继续走。换一个没有参与前面讨论的 Agent,反而更容易问出一些直接的问题:这个改动会不会破坏兼容性?失败时怎么处理?测试覆盖了什么?
于是,我把两件事分开了。
第一个 Agent 负责理解需求,找代码,列出影响范围和验收条件。第二个 Agent 只拿原始需求、项目约束和最终方案,从头检查。如果发现问题,方案就退回去修改。
flowchart TD
I["收到需求"] --> A["需求分析"]
A --> R["独立 Review"]
R -->|需要修改| A
R -->|通过| H["我来确认"]
H -->|调整方案| A
H -->|开始开发| D["开发"]
D --> C["代码 Review"]
C -->|存在问题| D
C -->|通过| V["验证与交付"]
这套做法能起作用,关键在于两个 Agent 看到的信息不同,分工也不同。一个提出方案,另一个专门挑问题。
有些决定我还是希望自己来做。比如要不要改变现有交互,值不值得承担兼容成本,或者要不要顺手扩大需求范围。这些问题没有唯一正确答案,知道更多代码也不一定能替我决定。
所以图里的“我来确认”并不是自动化没做完,而是我特意留下来的停顿。Agent 先把事实和风险摊开,我做选择,然后再让流程继续。
问题其实不在 Prompt
一开始,我只想把常用 Prompt 保存下来:需求分析一份,方案 Review 一份,开发实现一份。需要时选一个模板,总比每次重新写方便。
后来我发现,真正麻烦的是那些藏在文字外面的判断:
- 现在应该调用哪个 Agent;
- 它需要看到哪些内容;
- 上一步要交付什么结果;
- Review 没通过时应该回到哪里;
- 哪一步必须停下来等我确认;
- 执行中断以后是继续,还是从头再来。
Prompt 能告诉 Agent 这一步要做什么,却不会替我管理整项任务。真正反复发生的,是阶段之间的流转。
我开始把这件事当成一条普通的工作流来处理:每一步都有输入和产物,也有进入下一步的条件。Agent 只管眼前的任务,不必知道后面还会调用谁。
流程有了,还得有个地方保存整项任务的信息。我把一项从需求理解走到开发交付的任务叫作 Issue。它不一定对应 GitHub Issue,也不是 Agent 自带的概念,只是这套工作流中的一次任务。
原始需求、当前阶段、每轮产物和执行状态都放在同一个 Issue 里。Agent 不必理解这个概念;工作流会取出当前阶段需要的信息,再把结果存回去。
有了这个载体,流程配置大概可以写成这样:
1 | |
这份配置并不复杂,却明确写出了每个阶段的去向。流程不再由 Prompt 末尾的一段话临时指挥,而是按照外部状态和规则运行。
我可以换掉某个 Agent,也可以调整 Review 标准,不用重新编排整段对话。Prompt 只需要服务当前阶段,不再承担整个流程。
Agent 之间应该传什么
手工操作时,最顺手的办法是复制上一段聊天记录。看起来信息最完整,也不需要额外整理。
但聊天记录里有太多过程:试过又放弃的方案、还没证实的猜测、工具输出,以及中途被推翻的结论。下一个 Agent 虽然看到了全部内容,却不一定知道最后该信哪一段。
我后来更倾向于让每个阶段交付一份“成品”。
需求分析的结果应该写清楚要解决什么、不做什么、会影响哪里、有什么风险、最后怎么验收。Review 的结果则要明确给出通过或退回;如果退回,还要指出问题和下一轮必须补齐的内容。
这样做以后,前后两个 Agent 不需要共享完整对话。后一个 Agent 只读取原始问题和上一阶段确认过的结果,工作流也能根据 Review 的结论决定继续还是返回修改。
同一个方案改了几轮,也不必互相覆盖。每轮产物都可以单独保存。回头看时,我能知道某个决定是什么时候加进去的,而不是在一条很长的聊天记录里猜。
我现在会用一个很简单的问题判断产物是否合格:如果关掉当前会话,只看它提交的结果,我还能不能接着往下做?如果不能,那它交付的多半还只是聊天过程。
让 Agent 真正进入仓库
流程只处理文档时,事情还比较简单。等开发 Agent 开始修改代码,目录和进程也得一起管起来。
从技术上说,只读的分析任务可以共享代码目录。但我最后还是为每次 Agent 执行准备了独立的 Git worktree。这样分析和 Review 面对的是确定的代码状态,开发任务也不会碰到我当前目录里的未提交修改。
worktree 共享原仓库的 Git 对象,不需要重新 clone 一份代码,但每次执行都有自己的工作目录、索引和分支状态。
1 | |
目录隔离以后,还有进程的问题。我用的 Agent 是长时间运行的交互式命令行程序,不能让它的生命周期跟着浏览器连接一起结束。
现在的实现用 tmux 托管 Agent 进程。浏览器断开后,Agent 仍然可以继续运行;我回来时可以重新接上终端查看输出,必要时也能自己接管。
把这几部分接起来后,我发现一次阶段任务至少包含四类状态:
flowchart TB
J["阶段任务"] --> S["任务状态
现在进行到哪一步"]
J --> G["Git Worktree
代码改成了什么样"]
J --> T["tmux 会话
进程是否还在运行"]
J --> E["Agent 会话
它记得哪些上下文"]
这几类状态会各自变化,不能混成一个笼统的“运行中”。代码还在,tmux 进程可能已经退出;Agent 上下文能够恢复,也不代表原来的 worktree 还能继续使用。
即使进程正常结束,阶段也可能没有完成。因此,开发 Agent 结束前要提交结构化结果,写明修改了哪些文件、做过哪些验证、测试是否通过,以及还有什么问题没有解决。进程退出码只能说明程序已经结束,不能代替阶段结论。
目前先做到这里
现在,需求分析、独立 Review、worktree 隔离、tmux 托管和结构化结果都已经接进了流程。一项需求可以从分析走到开发,不再需要我守着几个窗口来回转发消息。
做到这里,我才更清楚地看到:worktree 和 tmux 能让 Agent 稳定执行,却解决不了阶段之间的流转。如果仍然靠人复制内容,后台进程运行得再稳,我还是那个负责转发消息的人。
还有一些问题没有处理,比如中断以后怎样恢复,任务结束后什么时候清理环境。目前我只有初步设计,不想在这篇文章里把它们写成已经验证过的结论。
回头看,整件事是从“少复制几次 Prompt”开始的。Prompt 可以换,Agent 也可以换,我想固定下来的是几个步骤之间的关系:先分析,再独立 Review,遇到取舍时停下来,确认以后再继续。
只要下一次处理需求时,我不用再充当 Agent 之间的消息队列,这套工作流就已经解决了一个很具体的问题。