去年年初,我们团队做了一次不太常见的重构:重构对象不是某个服务,也不是某段代码,而是开发团队自己干活的方式。我们试着把需求拆解、方案评审、编码、测试、交付这些原本需要人不断同步信息的工作,交给一套内部代号叫 Agency Agents 的多智能体协作系统来推进。这篇文章想把它的架构设计、落地过程和踩坑经历完整写出来。
Agency Agents 不是什么玄学,本质上是把“一个开发团队如何协作”这件事显式地建模成角色、契约、路由、状态和验收规则。如果你正在考虑多智能体系统,或者被各种 Agent 概念绕晕,可以参考我们的做法:哪些环节值得自动化,哪些地方必须留给人,又有哪些坑是只有在真实项目里才会撞上的。
1. 传统研发协作的真正瓶颈:不是人手不够,是上下文断裂
1.1 为什么单Agent到了某个阶段就推不动了
我们最开始尝试的并不是多智能体,而是一个“超级助手”:把一整份需求文档丢给一个大模型,让它一次性产出方案、代码和测试。结果并不理想。代码表面上能跑,但方案和需求对不上,甚至会把上一个项目的旧逻辑带进新代码。
后来复盘,问题不在模型能力,而是我们把任务结构想得太简单了。开发团队真正能高效运转,靠的不是某个人特别聪明,而是信息在角色之间传递时损耗小。产品说“用户点击按钮后要看到反馈”,开发听到的可能是另一个意思,测试理解的又是第三个版本。人工协作时,这种偏差依靠会议、评审来纠正,但每一次纠偏都消耗时间。
到了单 Agent 场景,问题被放大了:一个模型要同时记住需求、技术约束、代码现状、测试口径,上下文窗口再大也扛不住。而且它没有“反馈回路”,自己写完代码自己评审,等于既当运动员又当裁判。我们试了几轮,发现单 Agent 在中型模块上勉强能干活,一旦需求稍微复杂,输出的稳定性就断崖式下跌。
1.2 从“流水线”到“Agent团队”的思想转换
传统开发流程通常被描述成一条流水线:需求 → 设计 → 编码 → 测试 → 发布。这条流水线强调的是“顺序执行”,而不是“协作”。但真实开发团队不是这么运转的:架构师会回头质疑需求,测试会反馈代码问题,开发会补充设计遗漏。信息流是网状的,不是直线型的。
Agency Agents 借鉴的正是这种网状协作。我们不再试图用一个 Agent 完成所有事,而是定义了一批各有边界的角色:需求分析 Agent 只负责把模糊输入整理成结构化需求;架构 Agent 只负责基于需求产出技术方案;研发 Agent 只负责按方案写代码;评审 Agent 只负责提出修改意见;测试 Agent 只负责验证行为是否符合验收标准。
每个角色都不掌握全局,但它们通过事件和共享状态交换信息。这样做的好处是,上下文可以被切碎到每个角色够用的程度,任何一个 Agent 的失败都不会拖垮整个系统,因为失败会落到一个可观测的任务单元里。
1.3 这套系统适合谁,不适合谁
先说适合的场景。我们团队当时负责的是一批流程已经相对固定的业务模块,需求格式、编码规范、测试要求都有历史沉淀。这类模块很适合多智能体化,因为“协作规则”是明确的,Agent 只是在执行已经被人总结过无数遍的流程。
不适合的场景也很多。如果需求本身极不明确,连人都说不清验收标准,多智能体系统只会加速混乱,因为每个 Agent 都会基于自己那部分碎片信息作出自信但错误的判断。小规模原型探索也没必要上这套系统,两三个 Agent 的编排成本可能比人工协作还高。我们内部的原则是:一个流程如果还没有稳定跑通三个月,就不要急着交给 Agent。
2. Agency Agents的整体架构:编排层、执行层与记忆层各司其职
2.1 架构总览:五层各干什么
Agency Agents 虽然听起来复杂,但架构上我们只分了五层。接入层负责接收外部请求,把自然语言需求转成系统内部的标准任务单;编排层是大脑,决定每个任务该交给谁、失败了怎么办;执行层是所有 Agent 的运行时,负责调用模型和工具;记忆层保存短期会话、项目级长期记忆和全局状态;审计层记录每一次决策、每一次工具调用,供事后追溯。
这五层里最容易被人忽视的是记忆层和审计层。很多团队搭多智能体时只关注“Agent 能干什么”,却忽略了“Agent 记着什么”和“Agent 干过什么”。缺少记忆层,Agent 之间就像一群没有共同文档的员工,各自凭印象干活;缺少审计层,出了问题根本没办法复盘,只能靠猜。
2.2 角色即配置:用类与Profile定义Agent
我们在系统里把每个 Agent 定义成一份配置,而不是写死在代码里。这么做是希望角色边界能随时调整,毕竟不同项目的流程细节不一样。下面是一段简化过的角色配置示例:
agents: requirement_analyst: model: backend-v3 context_window: 32k memory: project_memory tools: [read_repo, search_docs, write_spec] output: requirement_spec tech_lead: model: backend-v3-max context_window: 64k tools: [read_repo, read_spec, write_plan] output: architecture_plan developer: model: backend-v3 context_window: 128k tools: [read_spec, read_plan, list_files, edit_code, run_tests] output: code_changes配置里最关键的不是模型选择,而是tools权限列表。一个 Agent 能调用什么工具,决定了它的能力边界。后面我会专门讲权限失控带来的事故,这里先记住一个结论:角色配置的核心是收敛,不是堆能力。
我们也没有给所有 Agent 都用最强模型。需求分析 Agent 用中等模型就够,技术方案 Agent 需要用更强模型来权衡利弊,而研发 Agent 反而要选上下文窗口更大的模型,因为它要同时读方案、读代码、跑测试。不同模型各司其职,成本比全部拉满低很多。
2.3 通信与状态:事件总线、全局状态与消息补偿
多个 Agent 之间怎么通信,是最容易走偏的设计点。我们踩过的最深的一个坑,是让 Agent 直接调用彼此的接口。比如需求分析 Agent 处理完,直接调架构 Agent 的接口。表面看链路很清晰,一旦某个环节失败,整个调用链就被卡死了,而且问题发生在哪一层很难定位。
后来我们改成事件总线模式。Agent 之间不直接调用,而是把结果作为事件发布到总线上,编排层根据订阅关系决定下一步触发谁。每个事件都带唯一的 trace_id,可以从需求入口一路追踪到交付物。这样即使某个 Agent 处理失败,消息也不会丢失,而是进入补偿队列,由编排层决定重试还是转人工。
全局状态我们用的是一套带命名空间的键值存储。每个写入状态的操作必须声明自己是哪个 Agent、属于哪个项目、是哪一轮任务。这个设计听起来笨,但在排查问题上帮了大忙。没有命名空间的时候,两个 Agent 往同一个 key 上写数据,后写的人会覆盖先写的人,而且毫无感知。
2.4 一次典型任务流转的时序描述
为了让你对整体运行有画面感,我描述一次典型任务:收到一条用户需求后,接入层先把需求标准化成 ticket,包含描述、优先级、验收标准初稿。编排层根据 ticket 类型路由给需求分析 Agent。分析 Agent 读取项目既有文档,产出结构化需求规格,发布事件。
架构 Agent 订阅到需求规格事件后,读取代码库结构和历史架构决策,产出技术方案。研发 Agent 接着按方案修改代码,每改完一个文件就运行相关测试,所有测试通过后发布“代码就绪”事件。
评审 Agent 把代码变更和需求规格放一起对照审查,提出意见。如果意见为空,意味着可以交付;如果有意见,事件回传给研发 Agent 继续修改。最终,编排层把交付清单发给人工确认,由人来执行最后一步。这里每一步都不是串行阻塞,而是围绕事件驱动的异步推进。
3. 把开发流程改写成Agent协作脚本:拆解、路由与验收
3.1 从需求到验收:六个角色的职责边界
如果把整个系统当成一个开发团队,那么角色划分就是组织架构。我们当时用六个角色跑通了完整链路:需求分析 Agent、架构 Agent、研发 Agent、评审 Agent、测试 Agent、交付 Agent。每个角色都有明确的输入、输出和兜底策略。
| 角色 | 核心职责 | 主要输入 | 主要产出 | 兜底策略 |
|---|---|---|---|---|
| 需求分析 Agent | 将模糊需求转成结构化规格 | 用户工单、历史文档 | 需求规格、验收标准 | 补采问题超过3轮转入人工 |
| 架构 Agent | 输出技术方案与风险点 | 需求规格、代码结构 | 架构方案、变更清单 | 方案引用不存在文件则重试并告警 |
| 研发 Agent | 按方案实现代码与自测 | 架构方案、任务说明 | 代码变更、测试结果 | 测试失败时最多重试2次 |
| 评审 Agent | 对照需求和方案审查代码 | 代码变更、需求规格 | 评审意见列表 | 意见循环超过5轮自动升级人工 |
| 测试 Agent | 按验收标准执行验证 | 测试计划、运行环境 | 验证报告 | 环境异常转人工运维 |
| 交付 Agent | 生成发布清单,等待人工确认 | 验证报告、变更记录 | 发布说明、回滚方案 | 任何一步失败则阻塞发布 |
这个表背后有一条原则:职责只保留“完成一项可交付物”所需的最小范围。比如评审 Agent 不需要写代码能力,测试 Agent 不需要架构规划能力。角色职责越窄,上下文越干净,失败定位也越容易。
3.2 任务路由规则与失败兜底
有了角色之后,编排层要回答一个问题:一个任务进来,到底该推进给谁?我们用的不是自然语言让 Agent 自行判断,而是一张显式路由表。每个入站任务都带 type、project_id、source_agent、required_permission 等元数据,路由逻辑直接用代码表达。
def route_task(task): if task.type == "requirement_refinement": return "requirement_analyst" if task.type == "architecture_review": return "tech_lead" if task.type == "implementation": return "developer" if task.phase == "review": return "reviewer" if task.phase == "test": return "tester" raise UnroutableTaskError(task.id)这段伪代码朴素,但可靠。一开始我们想让路由也由大模型决定,后来发现模型对路由的判断不稳定,经常把测试任务发给研发 Agent,或者把评审任务发给需求 Agent。手工维护路由表虽然不够“智能”,却保证了行为可预期。
失败兜底同样重要。我们给每个任务设置了最大重试次数和超时时间。比如评测任务最多重试 2 次,评审循环最多 5 轮。达到上限后,任务自动进入人工接管队列。我们团队每天有一个固定的值班角色处理接管队列,这个角色不会去改 Agent 的代码,而是把卡住的任务手动推进到下一个合理的环节。
3.3 让结果可验证:结构化输出与签名校验
多智能体系统里最危险的信号,是 Agent 之间的交流使用自由文本。自由文本看着灵活,但下游 Agent 解析时很容易出错,而且错误是静默发生的。我们要求所有 Agent 之间传递的消息必须是结构化对象,并且带版本号和校验字段。
例如需求分析 Agent 的产出不是一段散文,而是一个包含目标、范围、验收条件列表的数据结构。研发 Agent 的提交说明也不是“完成了功能”,而是一份包含文件路径、变更类型、测试结果的结构化报告。编排层对每份产出做 JSON Schema 级别的校验,不满足结构就直接丢弃,并触发重试。
这种设计让系统里的人机协作也变得顺畅。人工确认交付清单时,看到的不是一个聊天窗口里的长篇大论,而是一份字段清晰的审批单。哪些需求被实现、哪些测试通过、回滚步骤是什么,一目了然。我们后来总结过一句话:Agent 之间要讲协议,不要讲段子。
4. 落地过程中最难啃的三块硬骨头:循环、污染与权限
4.1 Agent互相“踢皮球”:循环调用的定位过程
多智能体系统上线后的第一次重大事故,发生在事件总线上。某个深夜,总线消息量突然飙到了平时的几十倍,我们第一反应是流量攻击,查了半天发现是内部两个 Agent 在互发消息。
现象如下:需求分析 Agent 发布一条“需求约束不足”的事件,架构 Agent 收到后回复“需要更明确的验收标准才可估算工期”,需求分析 Agent 又基于这个回复生成了新的约束补丁,架构 Agent 又指出新的问题。两个 Agent 都认为自己只是按规则“询问必要信息”,但实际上陷入了无限循环。
我们通过 trace_id 过滤出整条链路,一次一次查看两边的输入输出,最后定位到根因:这两个 Agent 都调用了一个共同的意图分类工具,工具会鼓励 Agent 在信息不足时主动追问,但系统里没有定义“追问次数上限”。修复方案分三层:给每个协作任务增加最大交互轮数,超过后强制升级人工;把“请求补充信息”从自由对话改成半结构化表单,必须指定缺失字段;在编排层增加循环检测器,如果同一对 Agent 的消息模式在过去 N 轮内重复出现,就中断链路并告警。
这次事故让我意识到,多智能体系统的循环问题比单线程代码里的死循环更难发现,因为它不是同一个 Agent 重复执行同一段逻辑,而是两个 Agent 在互相放大彼此的臆测。
4.2 上下文污染像雪崩,如何用命名空间和快照止血
第二个折磨我们的问题是上下文污染。现象很诡异:评审 Agent 的评审意见里,居然出现了只有需求分析 Agent 才应该知道的信息。起初我以为是模型串了,后来查了状态存储才发现,两个 Agent 共用了同一个 key 来缓存临时信息,后写入的用户偏好数据被评审 Agent 当成了项目级事实。
这就是典型的“多人共用一本草稿纸”问题。每个人都在上面演算,可你永远不知道哪行是演算草稿,哪行是正式公式。我们当时的止血措施是先给所有状态 key 加上命名空间和版本号,规则是project_id:agent_id:purpose:version,任何 Agent 只能读自己的命名空间,以及通过事件总线发布的、带 schema 的共享消息。
接着我们引入任务级快照机制。每个任务开始时,系统会从记忆层加载最近一次校验通过的项目快照;任务跑完后,只有经过校验的产出物才被写回共享存储。这样即使某个 Agent 在工作过程中把临时状态写乱了,也不会影响其他 Agent。
还有一个细节:大模型读上下文是有极限的,不是给得越多越好。我们后来给“读历史”这个操作设定了一个裁剪策略,只保留和当前任务直接相关的摘要。这个策略见效很快,上下文污染问题基本绝迹,而且模型输出的质量反而提升了。
4.3 工具权限风暴:最小授权原则的工程化
如果只能给出一条安全底线,我会选工具权限。我们曾经出过一次非常低级的事故:测试 Agent 在执行回归测试时,误调用了生产环境的部署工具,把一个灰度环境标记成了新版本。原因说起来很丢人,最初为了图省事,所有 Agent 共享一个全局工具注册表,谁都能调用所有工具。
事故发生之后,我们把工具权限彻底重构成了配置的一部分。每个 Agent 的配置里必须显式声明自己可以用哪些工具,运行时还有一个统一权限拦截层,对每次工具调用检查 scope。以下是我们当时总结的权限分级:
| 权限级别 | 工具示例 | 触发条件 |
|---|---|---|
| L1 只读 | 读取仓库、检索文档 | 任何相关 Agent 可直接调用 |
| L2 工作区写入 | 修改代码、运行测试 | 仅限研发、测试 Agent,且必须在任务工作区内 |
| L3 半危险 | 生成发布单、修改配置 | 必须附带完整变更说明,并记录审计日志 |
| L4 高危 | 部署生产、变更配置 | 任何人不能直接调用,必须由外部人工审批事件触发 |
L4 的处理方式很关键:Agent 可以“请求”交付,但不能“执行”交付。交付 Agent 在确认验证通过后,会生成一份审批单推给人工,由人工在独立的审批界面点击确认,系统才会真正调用部署工具。这个设计牺牲了一点自动化率,但换来了安全底线。
5. 实测数据与团队角色重构:开发者的日常发生了什么变化
5.1 我们从指标上看到的提升
在系统稳定运行三个月后,我们做了一次内部复盘。口径不同团队没法直接对比,但有几个趋势是明显的:常规需求从接单到第一个可评审版本的前置时间,缩短了大约四成;需要人工介入的次数从原来的一次需求七八次,降到了两三次;测试反馈的问题里,单纯的遗漏性错误明显减少,因为测试 Agent 每次都会严格按验收标准逐条执行。
但也有表现不好的场景。凡是需要跨团队线下协调、或者需求本身高度依赖行业经验的活儿,系统不但没提速,反而因为反复补充信息而变慢。这提醒我们,多智能体系统不是通用的效率放大器,它只适合那些“协作规则已经被理解得很透彻”的工作。
数据从另一个角度也告诉我们,自动化率不是唯一目标。我们关注的是端到端交付时间和返工率,而不是“让多少个 Agent 在跑”。如果 Agent 跑得很热闹但没有产出可交付物,那就是浪费。
5.2 开发者的工作从“写代码”变成“定规则和验收”
重构开发团队这件事,最终被人感受最深的不是系统本身,而是团队成员的日常工作变了。以前开发者的主要精力在实现功能上,上线后还要反复排查环境问题。现在,大家的精力被重新分配到三类事情上:定义路由规则、编写验收校验器、处理人工接管队列。
写代码依然重要,但代码更多是给校验器用的:把“用户输入无空指针”写成可执行的测试,把“协议字段必须具备版本号”写成 schema 校验。Agent 写实现,人来写验收,这个分工听起来有点颠覆,实际跑起来却比想象中顺畅,因为验收规则永远掌握在人手里。
测试工程师的角色变化更明显。他们不再只是跑测试,而是变成“验收规则评审者”,去检查测试 Agent 制定的测试计划是否覆盖了关键路径,是否漏掉了边界条件。这个转变要求测试人员更懂业务,而不是更懂某个测试工具。
5.3 留给我们自己的三条底线
这套系统跑了一年多,我们总结了三条底线,任何项目只要触碰其中一条,我们就会停下来重新讨论。
第一条,任何任务必须具有可判定的完成条件,否则不进系统。如果连人都说不清“怎样算完成”,就不要指望 Agent 能自己定义。
第二条,任何面向外部用户的交付动作,必须有人工确认环节。Agent 可以做到 99% 的准备,但最后 1% 的确认和担责,必须留给人类。
第三条,所有 Agent 行为必须可回溯。我们要求每次工具调用、每条事件、每次模型输出都记录 trace_id。宁可牺牲一部分执行速度,也不能牺牲可观测性。
我后来回想,这三条里面最容易被忽略的是第一条。很多团队把大量精力花在调模型、选框架上,却不愿意花时间把需求写成结构化的验收标准。但在我们的经验里,结构化验收标准带来的收益,比换一个更贵的模型大得多。
如果你也想做类似的多智能体系统,我给的建议是从一个极小的闭环开始:选择一个流程成熟、失败后果可控的内部场景,设计两个 Agent,一个负责产出,一个负责审查,中间用严格的事件和状态协议串起来。先把链路跑透,再考虑加角色、扩场景。
最后说一个我自己的体会。Agency Agents 真正改变我们团队的,不是让我们彻底不用写代码了,而是逼着我们把“开发团队如何协作”这件事讲清楚了。很多模糊的流程,在写角色配置、写路由规则、写验收函数的过程中,变得前所未有地明确。哪怕哪一天这套系统被完全拆掉,留下的这套协作协议,依然会让团队跑得更快。