尽管我们可以用最好的模型完成所有的事情,但是约束才是我决定写这篇文章的一个出发点:约束越多,问题就越好玩。
构建多 Agent 系统时,我们面临三大核心约束:
结合最近正在构建的 Routa( https://github.com/phodal/routa ),分享我的一些新想法,以及现有一些实践,诸如于如何通过 Specialist 角色化、状态外置 和 MCP 跨 Agent 通信,将 “多 Agent 协作” 打造成可演进的工程系统,而非一次性 Prompt 的堆叠。
在设计 Routa 多 Agent 系统作为软件开发平台的时候,我一直想解决一个成本的问题 + 协同的问题:
事实上,我并不想为任何的 token 付钱 —— 作为一个开发者,用自己的钱去支持一个淘汰自己工具的公司,是愚蠢的。如果是用于工作的工具, 公司应该为我付钱;如果是用于生活的话,我只想休息。
在这种场景下,我的 Coding Agent 工具就比较杂乱了:
在没有公司完全买单的 Cursor 之后,我需要一种更好的方式,能在一个月非常好地利用、编排工具,这样的话 Kiro + Copilot 提供的 Opus/Sonnet,就能发挥最大价值 —— 当然要我说最好用的还是 Augment Code,但是只是 backup,Claude Code 虽然强,但是上下文的时间成本太高了。怀念 Cursor 的前端能力。
但是我觉得这个问题对于大部分人来说是相似的,诸如于:
在节省最好的编码模型 token 的同时,利用好高性价比的国产模型作为 background agent 来做更多的事情。
基于此作为出发点 Routa 与其它多 Agent 系统有着非常大的区别。整个 Routa 的架构有点类似于现在的人类团队的构成。Routa 本身是没有 Coding Agent,它有:
在这种模式下,我们要解决一个问题:
即使单个组件不可靠,也能搭建出高度可靠的系统 —— Leslie Lamport
通俗也来说:
如何把不同能力的 Agent 组合起来,让强模型做规划,弱模型做执行,在节省成本的同时保证质量?
因此,Routa 的核心原则可以总结为:
作为一个 AI 时代身处在没落的咨询行业的技术专家,我一直相信,可演进性才是我的竞争力,也是构建系统的核心 —— 尽管,供应商绑定(Vendor Lock-in) 也是一个非常“不错”的商业模式,但是那可是屎山。
既然,我们不想被某个商业、闭源的工具绑定,诸如于 Claude Code,那么我们就需要设计成可演进的系统,ACP 协议是我们根据业内的趋势发现最好的一种 方式(没有之一),通过 ACP 我们可以灵活地替换 AI 编程工具。按能力与场景灵活选择:
基于此模式,只需要设计一个更好的协作方式协同就行了。诸如 Routa 采用的 MCP 方式:
所有 Provider 都通过统一 MCP 配置接入 Routa 协调服务器,无需修改 Agent 逻辑即可切换。
{
"name": "routa-coordination",
"type": "http",
"url": "http://localhost:3000/api/mcp",
"env": {
"ROUTA_WORKSPACE_ID": "ws-123"
}
}
其核心设计要点:
动态发现与内置 Presets:
Routa 支持 动态发现 Agent,通过 ACP Registry 获取最新版本和配置,实现无缝扩展。同时,Routa 提供 内置 Presets 定义标准 Agent 的启动方式:
核心设计要点:
职责清晰,角色独立;组合灵活,系统可扩展。
在复杂系统中,单一角色无法覆盖所有场景。Routa 将 Specialist 设计为可组合、可扩展的核心单元,每个 Specialist 专注职责,按需组合。如下是 在上个阶段(上周)中,Routa 参考 Augment Code 的 Intent 内置的 Specialist:
| 角色 | 核心职责 | Model Tier | 工具权限 |
|---|---|---|---|
| ROUTA (Coordinator) | 规划任务、委派子 Agent | SMART | 无文件编辑,仅委派 |
| CRAFTER (Implementor) | 编写实现、落地代码 | FAST | 完整文件编辑权限 |
| GATE (Verifier) | 审核、标准检查 | SMART | 只读 + 消息通信 |
| DEVELOPER (Solo) | 独立规划与实现 | SMART | 完整权限,不委派 |
随着,我们添加了 Event/Hook driven 的功能,我们添加了自定义 Specialist,如:issue-enricher,当新创建了 issue 之后,可以自动调用 Agent 来分析代码:
GitHub Webhook (issues.opened)
↓
handleGitHubWebhook() — 匹配 trigger rules
↓
WorkflowExecutor.trigger() — 创建 WorkflowRun
↓
BackgroundTask (每个 workflow step 一个)
↓
ACP Process — 使用 specialist 配置
而在我们的实现里,当你使用了 Claude Code 之后,Specialist 也是一个 Skill。在我们的设计里,Specialist 和 Skill 使用相同的 Markdown + YAML frontmatter,可以互相转换。
基于 ACP 协议与我们的事件驱动架构,以及未来 trace 方案,我们参考 Agent Trace 标准,遵循 可重放、可回滚、可审计 的原则, 将关键状态外置到持久化介质,而非完全依赖内存。
因此,在存储上分为:
.routa/traces/{day}/traces-{datetime}.jsonl这里的 Trace 是协作事实来源,而非调试日志:
由于我们是 Tauri + Next.js 双架构,所以还需要:
状态外置保证跨环境一致性,可在本地文件或 Serverless 数据库之间无缝切换。
对于 issue 等其它方式也是相似的,基于 Agents.md 与代码库中的 issues/ 目录,记录 Agent 遇到的问题,方便未来排查。 而在 Agent 协同上, 我们需要的东西更多,诸如 explore agent 生成的 context 可以先存储在临时目录,或者当前项目中等。
每个 Agent 只接收必要上下文:
要点: 避免浪费 Token,精确控制信息。
流程不是一次性指令,而是可复用能力的组合与角色化绑定。
大部分 Specialist 本质上类似于 SKILL.md,是一个可复用的能力单元。但是,Specialist 是一个角色,它是一个有状态的、可交互的、有权限的 Agent。它可以调用不同的 Skill,也可以不调用 Skill。也因此,Routa 的流程复用遵循三层绑定关系:
| 层级 | 定义 | 载体 | 生命周期 |
|---|---|---|---|
| Prompt | 一次性指令文本 | 用户输入或代码字符串 | 单次使用 |
| Skill | 可复用的能力单元 | SKILL.md 或 ~/.claude/skills/ |
跨项目复用 |
| Specialist | 角色 + 权限 + 工具 | Database / YAML / 硬编码 | 系统级绑定 |
其核心绑定流程如下:
roleReminder 强制行为约束我们过去习惯写线性脚本,一条条指令顺序执行。但现实里,流程总是充满不确定——任务会延迟、结果会变化、不同角色需要协作。于是,我开始思考: 如果把流程当作事件流,会怎样?
在日常开发中:我们推送代码到 GitHub,触发了 CI/CD 流程、代码扫描、自动部署,甚至还有通知机器人。每一次推送都是一个事件, 每个服务响应事件的方式都不同,但整体流程依然可靠。事件驱动,就是在软件开发中把这种自然触发机制抽象出来,让每一步可组合、可观察。
所以,让每个步骤都有独立的 Agent 去实现,问题就变得非常简单了。
在这个架构里,EventBus 就像系统的神经网络,每个 Agent 都是独立的节点。事件在网络里流动,有的只触发一次,有的需要优先响应, 有的要等一组任务完成——它们的订阅方式灵活多样:
想法很简单:让事件推动流程,而不是让人盯着轮询。
不同系统、不同 Provider 事件格式不一样。EventBridge 就像一座桥,把各种事件统一成标准格式,让我们可以用同一套逻辑去处理:
事件本身是无序的,但任务依赖必须被尊重。Workflow 就是把事件背后的秩序梳理清楚:
BackgroundTaskdependsOnTaskIds 明确依赖parallel_group 并行,提高效率小技巧:顺序和并行可以共存,事件流让协作不再堵塞。
即便是事件驱动,也不能完全放开。无限嵌套的任务会把系统拖垮,太多并行会让调度爆炸。于是我们加了几个边界:
export const MAX_DELEGATION_DEPTH = 2; // 防止无限递归
export const MAX_PARALLEL_TASKS = 10; // 控制任务图规模
export const DEFAULT_TASK_TIMEOUT = 300; // 超时保护
核心思想:事件驱动让系统灵活,但边界保证长期可控。
Token 并不是免费的算力,而是一种稀缺资源。
因此,核心问题是:如何在节省成本的同时保证系统可靠? 答案是——为不同任务配置专门的 Specialist,并通过模型可配置实现资源精细调度。
每个任务场景对应一个专门的 Specialist:简单任务用性价比高的模型,复杂任务用强模型,重复任务交给自动化 Specialist。
核心思想:不要用万能模型解决所有问题,而是根据任务需求动态选择最合适的模型和工具。
Routa 的设计允许每个 Specialist 在运行时选择和切换模型:
问题就变成了:
把简单任务交给便宜模型,把复杂任务交给强模型,把重复任务交给自动化 Specialist。 模型可配置 + 任务驱动 Specialist + 事件调度,让有限资源发挥最大价值。
在多 Agent 协作中,工具不仅仅是一个个函数,而是 Agent 与外部世界交互的接口。每个 Specialist 需要不同的能力,也需要不同的权限边界 —— ROUTA 不应该能直接修改文件,GATE 不应该能执行命令,CRAFTER 需要完整的编辑权限。
但工具本身是中立的,它不关心谁在调用它。真正关心的是:谁来调用、能做什么、应该被限制什么。
协作是逻辑,执行是能力,两者不应该混在一起。
在 Routa 的架构中,我们将通信层与执行层明确分离:
这种分离带来的好处是:
要点: 协作是逻辑,执行是能力,两者不应该混在一起。
核心 MCP 工具示例:
| 工具 | 用途 | 调用者 |
|---|---|---|
| delegate_task_to_agent | 委派任务给子 Agent | ROUTA |
| send_message_to_agent | 跨 Agent 消息通信 | 所有 |
| report_to_parent | 向父 Agent 报告执行结果 | CRAFTER, GATE |
| list_agents | 查看当前 Agent 状态 | ROUTA |
| read_agent_conversation | 读取 Agent 对话历史 | ROUTA, GATE |
| set_note_content | 创建/更新任务 Note | ROUTA, DEVELOPER |
| subscribe_to_events | 订阅系统事件 | ROUTA |
我不想做一个“自动回复机器人”。 当一个 GitHub Issue 出现时,我希望它真的被理解,而不是被总结。
于是我把它接入 Routa 的事件系统:GitHub Webhook 进入 BackgroundTask,启动 ACP Process,由一个 DEVELOPER Specialist 执行完整流程。
这个 Specialist 不只是调用模型,而是按固定步骤工作:
Understand → Analyze(检索代码库)→ Explore(推导多种方案)→ Output(结构化写回)。
它可以是单 Agent,也可以拆成多个 Specialist 组成 Workflow。分析用 smart 模型即可,复杂推导再升级——资源是可调度的,而不是固定的。
这个例子对我来说验证了一件事:
多 Agent 协作不是堆模型,而是把“触发、角色、流程、资源”工程化。
当 Issue 被创建时,它不是被丢给一个模型,而是进入一个可以演进的团队系统。
写这篇文章,其实是因为我在一个很现实的环境里做选择:模型有成本,工具会变化,供应商会绑定,而个人开发者不可能无限制地为 token 买单。在这种约束下,我更关心的不是“哪个模型最强”,而是系统能不能演进。
Routa 的这些设计——事件驱动、Specialist 角色化、状态外置、模型分层、协议解耦——本质上都在回答同一个问题:如何把不稳定的模型能力,组织成一个稳定的工程系统。与其依赖某一个强模型,不如把规划、执行、验证拆分成不同角色;与其写一次性 Prompt,不如把流程固化为可复用的能力;与其把状态塞进上下文,不如让它成为可以回放和审计的事实记录。
多 Agent 协作如果只是堆模型,那它只是成本放大器。但如果它是可组合、可替换、可调度的结构,它就变成了一种新的开发组织方式。对我来说,这件事的意义不在于“更智能”,而在于“更可控、更可持续”。
在约束之下构建系统,反而更有确定性。这也是我做 Routa 的真正动机。
围观我的Github Idea墙, 也许,你会遇到心仪的项目