多 Agent 协作实战:任务编排与子代理系统
一、引言:一个 Agent 搞不定的事
在构建智能应用的过程中,我逐渐发现一个残酷的真相:单个 Agent 的能力存在天花板。即便你给了它最强大的模型、最丰富的工具集、最详尽的系统提示,它仍然会在某些场景下力不从心。
拿"写一篇技术文章"这件事来说。如果让一个 Agent 独立完成,它通常的做法是:打开浏览器搜索几个资料,然后一气呵成写完。看起来效率很高,但结果往往差强人意——调研不够深入、文章结构松散、技术细节经不起推敲。为什么?因为写一篇好文章本质上是一个多阶段工程:调研需要广度与批判性思维,规划需要全局视角,写作需要流畅表达,校对需要严谨细致。这些能力放在一个 Agent 身上,就像让一个人同时做 CEO 和校对员——不是不可能,但一定会顾此失彼。
这其实就是软件工程里最朴素的分治思想:把一个复杂问题拆解成若干子问题,每个子问题交给最擅长的执行者。在 Solon AI Harness 中,这套思想落地为任务编排(Task Orchestration)与子代理系统(Sub-Agent System)。通过 task 和 multitask 工具,主 Agent 可以动态委派子任务;通过 AgentManager,我们可以统一管理所有 Agent 定义;通过子代理的独立心智与记忆,每个"员工"都能在自己的领域里专注执行。
今天,我就用"技术文章写作团队"这个场景,完整演示 Harness 的多 Agent 协作能力。这也是 Harness 系列实战文章的第五篇,前面我们聊过基础对话、工具调用、记忆系统和 Prompt 工程,现在是时候把这些能力组合起来,构建一个真正的 Multi-Agent 系统了。
二、场景:一个"技术文章写作团队"
假设我们要开发这样一个系统:用户只需要给一个文章主题(比如"Solon AI Harness 的记忆系统实现原理"),系统就能自动完成一篇高质量的技术文章。整个流程分四步走:
- 调研阶段 —— 调研 Agent 搜索相关资料,输出调研简报
- 规划阶段 —— 规划 Agent 根据简报,输出文章大纲
- 写作阶段 —— 写作 Agent 根据大纲,输出完整初稿
- 校对阶段 —— 校对 Agent 审阅初稿,输出修改建议
这个流程天然是一个有向无环图(DAG),每个节点是一个子代理,节点间的依赖关系决定了执行顺序。Harness 的 multitask 工具天然支持这种编排——它允许主 Agent 一次性委派多个子任务,并声明任务间的依赖。
为了管理这些子代理,我们会用到 AgentManager。它的角色类似于"人力资源系统":注册 Agent 定义、查询可用 Agent、获取特定 Agent 实例。每个子代理通过 AgentDefinition 定义,包含独立的系统提示词和工具集,拥有自己的心智(System Prompt)和记忆(Session)。
整体架构大致如下:
用户输入
│
▼
┌─────────────────────────────────┐
│ 主 Agent(主编) │
│ - 理解用户需求 │
│ - 拆解任务 │
│ - 通过 task/multitask 委派 │
│ - 汇总结果 │
└─────────────────────────────────┘
│
├── task: 调研 Agent ──────────► 输出:调研简报
├── task: 规划 Agent ──────────► 输出:文章大纲
├── task: 写作 Agent ──────────► 输出:文章初稿
└── task: 校对 Agent ──────────► 输出:修改建议
值得注意的是,这些子代理都是在运行时动态创建的,并非事先注册好的固定实例。主 Agent 通过 task 工具调用时,会指定需要哪个子代理来执行,Harness 引擎根据 Agent 名称找到对应的定义,实例化后执行,执行完毕后可以释放。这种"按需创建、用完即走"的模式,让系统非常轻量和灵活。
三、实现:定义子代理 + 构建引擎 + 任务委派
现在我们来完整实现这个系统。代码将分为三个部分:
- 定义子代理 — 为每个角色创建
AgentDefinition - 构建引擎 — 配置 HarnessEngine,注册子代理
- 任务委派 — 主 Agent 使用
task工具编排流程
3.1 定义子代理
每个子代理需要一个 AgentDefinition 对象。核心要素有两个:systemPrompt 定义角色心智,metadata(name, description, tool...) 定义元数据。
// 1. 调研 Agent:负责搜索和资料整理
AgentDefinition researchAgentDef = AgentDefinition.builder()
.name("researcher")
.description("资深技术调研员,可以联网搜索")
.toolsAdd("websearch", "webfetch") // 需要搜索和网页抓取工具
.systemPrompt("""
你是一名资深技术调研员。你的工作是:
1. 理解用户的研究主题
2. 搜索相关技术资料和官方文档
3. 输出结构化的调研简报,包含:关键概念、技术原理、最佳实践、参考来源
4. 保持客观,区分事实和观点
输出格式:
## 调研主题
## 关键发现
## 技术原理摘要
## 参考来源
""")
.build();
// 2. 规划 Agent:负责制定文章大纲
AgentDefinition planAgentDef = AgentDefinition.builder()
.name("planner")
.description("技术文章策划编辑")
.toolsAdd("read") // 只需要读取调研简报
.systemPrompt("""
你是一名技术文章策划编辑。你的工作是:
1. 根据调研简报,设计文章结构
2. 确保逻辑连贯、层次分明
3. 输出详细大纲,包含:引言要点、各章节核心论点、示例规划、总结方向
输出格式:
## 文章标题建议
## 章节结构(含每段核心内容说明)
## 需要重点突出的技术点
## 建议的代码示例位置
""")
.build();
// 3. 写作 Agent:负责撰写初稿
AgentDefinition writerAgentDef = AgentDefinition.builder()
.name("writer")
.description("资深技术写手")
.toolsAdd("edit") // 只需要读取大纲
.systemPrompt("""
你是一名资深技术写手。你的工作是:
1. 根据大纲,撰写完整的技术文章
2. 语言专业、流畅,适合开发者阅读
3. 包含必要的代码示例和配置说明
4. 注意文章的可读性:适当分段、使用列表、突出重点
写作风格:
- 专业但不晦涩
- 简洁但不冷漠
- 用 "我们" 而非 "你" 拉近距离
- 多举实际例子
输出格式:完整的 Markdown 文章
""")
.build();
// 4. 校对 Agent:负责质量审查
AgentDefinition reviewerAgentDef = AgentDefinition.builder()
.name("reviewer")
.description("一名严格的技术文章校对员")
.toolsAdd("read") // 只需要读取初稿
.systemPrompt("""
你是一名严格的技术文章校对员。你的工作是:
1. 检查技术准确性:概念是否正确、代码能否运行
2. 检查文章结构:逻辑是否连贯、章节划分是否合理
3. 检查语言表达:是否有语病、用词是否准确
4. 给出具体的修改建议
输出格式:
## 总体评价
## 技术问题(逐条列出)
## 结构建议(逐条列出)
## 语言问题(逐条列出)
## 优先级排序
""")
.build();
你可能注意到了,我在每个 Agent 的 systemPrompt 里明确写了"你的工作是什么"和"输出格式"。这很重要——子代理的心智越清晰,主 Agent 委派时的沟通成本就越低。从工程角度看,这其实就是"契约式设计":每个子代理承诺自己能产出什么格式的输出,主 Agent 按契约调用即可。
3.2 构建引擎
有了子代理定义,接下来构建 HarnessEngine。这里有几个关键配置:
toolsAdd(ToolPermission.TOOL_ALL_FULL)— 启用全部工具权限,包括task和multitaskmemoryEnabled(true)— 开启记忆,让主 Agent 能记住对话的关键信息autoRethink(true)— 开启自动重新思考,当 Agent 遇到困难时自动调整策略
// 构建引擎,注册子代理
HarnessEngine engine = HarnessEngine.of("writing-team", "/data/workspace")
.systemPrompt("""
你是一个技术文章写作团队的主编(Editor-in-Chief)。
你的职责是管理整个写作流程,按顺序完成以下步骤:
第1步:调研 — 委派 researcher 子代理进行资料调研
第2步:规划 — 委派 planner 子代理制定文章大纲
第3步:写作 — 委派 writer 子代理撰写初稿
第4步:校对 — 委派 reviewer 子代理进行质量审查
每一部完成后,仔细审阅子代理的输出,确认质量后再进入下一步。
如果某个步骤的输出不满足要求,可以重新委派改进。
最终输出完整的文章和校对报告。
""")
.sessionProvider(InMemoryAgentSession::new)
.toolsAdd(ToolPermission.TOOL_ALL_FULL) // 启用所有工具,包括 task/multitask
.modelAdd(new ChatConfig()...)
.memoryEnabled(true)
.maxTurns(30) // 预留足够的交互轮次
.autoRethink(true) // 遇到困难时自动调整
.build();
这种设计让子代理系统非常灵活——你可以完全自定义,也可以复用内置能力,甚至可以混合使用。
3.3 注册子代理到 AgentManager
有了引擎之后,我们把上面定义好的子代理注册进去:
// 注册子代理定义
engine.getAgentManager().addAgent(researchAgentDef);
engine.getAgentManager().addAgent(planAgentDef);
engine.getAgentManager().addAgent(writerAgentDef);
engine.getAgentManager().addAgent(reviewerAgentDef);
// 确认注册成功
System.out.println("已注册子代理: " +
engine.getAgentManager().getAgents().stream()
.map(AgentDefinition::getName)
.collect(Collectors.joining(", ")));
// 输出: 已注册子代理: researcher, planner, writer, reviewer
除了自定义子代理,Harness 还内置了四个通用子代理:
| 名称 | 用途 | 适用场景 |
|---|---|---|
general |
通用对话与推理 | 通用的问答、分析任务 |
explore |
探索与信息收集 | 搜索、资料调研、代码阅读 |
plan |
规划与任务分解 | 制定计划、拆分步骤 |
bash |
Shell 命令执行 | 文件操作、代码运行 |
这些内置子代理可以作为"基座能力"来使用。如果你的自定义子代理不需要特殊心智,直接使用内置的就可以。
3.4 执行任务委派
一切就绪后,启动引擎:
// 用户输入
String userRequest = """
请写一篇关于「Solon AI Harness 记忆系统」的技术文章。
要求:
- 介绍记忆系统的核心概念和工作原理
- 包含完整的代码示例
- 适合 Java 开发者阅读
- 字数 3000 字左右
""";
// 创建会话并执行
AgentSession session = new InMemoryAgentSession();
String result = engine.prompt(userRequest)
.session(session)
.call();
System.out.println(result);
当主 Agent 收到需求后,它会按照 System Prompt 中设定的流程,依次调用 task 工具委派子代理。整个过程是自动的,我们只需要观察输出即可。
四、运行过程:从需求到成品的完整链路
为了更好地理解多 Agent 协作的内部机制,让我们深入到运行过程,看看每一步发生了什么。
4.1 主 Agent 的思考过程
当用户输入请求后,主 Agent 首先进行内部推理。由于我们开启了 autoRethink(true),它会先规划再执行。通过查看日志(Harness 默认会输出详细的运行日志),能够看到类似这样的过程:
[主Agent] 收到用户请求:撰写关于记忆系统的技术文章
[主Agent] 规划流程:
1. 调用 researcher 进行调研
2. 调用 planner 制定大纲
3. 调用 writer 撰写初稿
4. 调用 reviewer 校对
[主Agent] 开始第1步:调研
[主Agent] → 调用 task 工具,参数:
agent: "researcher"
task: "调研 Solon AI Harness 的记忆系统..."
4.2 task 工具的内部机制
当主 Agent 调用 task 工具时,引擎内部执行了以下操作:
1. 解析参数,获取目标 Agent 名称("researcher")和任务描述
2. 从 AgentManager 查找名为 "researcher" 的 AgentDefinition
3. 调用 extensionAdd 中注册的逻辑,通过 def.builder(engine).build() 构建子代理
4. 为子代理创建一个独立的子会话(sub-session),继承主会话的上下文
5. 用子代理执行任务,传入任务描述作为 prompt
6. 返回执行结果给主 Agent
关键点在于子会话的继承机制。子代理不是从头开始的——它能看到主会话中与任务相关的上下文,包括用户的原始需求、已经完成步骤的输出等。这保证了信息在流程中顺畅传递。
4.3 各子代理的实际输出
为了让你有更直观的感受,这里展示一下各子代理实际产出内容的风格:
调研 Agent 输出摘要:
## 调研主题
Solon AI Harness 记忆系统
## 关键发现
1. 记忆系统基于 InMemoryAgentSession 实现,支持会话级记忆持久化
2. 核心接口包括:AgentSession、SessionProvider、AgentMemory
3. 记忆类型分为:对话记忆、事实记忆、会话属性
...
规划 Agent 输出大纲:
## 文章标题建议
Solon AI Harness 记忆系统深度解析:从入门到原理
## 章节结构
1. 引言:为什么需要记忆系统(从一个对话困境说起)
2. 核心概念:Session、Memory、Provider 三角关系
3. 快速上手:10 行代码开启记忆
4. 深入原理:记忆的存储、检索与过期策略
5. 实战技巧:自定义 SessionProvider、多会话管理
6. 总结与展望
...
写作 Agent 输出初稿:
# Solon AI Harness 记忆系统深度解析
## 一、引言:从一个对话困境说起
"你刚才说什么来着?"
这大概是我们在对话中最不愿意听到的一句话。对于 AI 应用来说,...
校对 Agent 输出报告:
## 总体评价
文章质量良好,结构清晰,技术准确。建议修改以下问题:
## 技术问题(高优先级)
1. 第三节中 SessionProvider 的 SPI 配置路径写错了,应为 META-INF/services/...
2. 示例代码第 45 行缺少 .build() 调用
## 结构建议(中优先级)
1. 第四节原理部分篇幅过长,建议拆分为两个小节
...
4.4 主 Agent 的最终汇总
所有子代理执行完毕后,主 Agent 会汇总结果,输出最终回复给用户:
[主Agent] 所有步骤完成,输出汇总报告
[主Agent] 生成完整文章正文 + 校对报告
用户看到的最终输出是一篇完整的文章,附带校对报告。如果用户对某部分不满意,可以直接要求主 Agent 重新委派某个步骤——比如"第三部分的示例代码再完善一下",主 Agent 会再次调用 task 工具,仅重新执行写作步骤,保留其他步骤的成果。
五、进阶:通过挂载动态加载外部子代理定义
上面我们演示了在代码中硬编码子代理定义的方式。但在实际生产环境中,你可能会遇到这样的需求:
- 插件化:第三方开发者可以贡献自己的子代理,无需修改主程序代码
- 热加载:运行时动态添加新的子代理,无需重启应用
- 配置化:用 YAML/JSON 定义子代理,而非 Java 代码
Harness 的挂载系统(Mount System) 正好解决这些问题。通过 MountDir,我们可以将文件系统中的目录挂载到引擎中,引擎会自动扫描并加载子代理定义。
5.1 定义外部子代理文件
假设我们在 agents/ 目录下存放子代理定义文件,每个文件描述一个子代理:
# agents/researcher.yaml
---
name: "researcher"
tools: ["web_search", "web_fetch"]
---
你是一名资深技术调研员...
# agents/writer.yaml
---
name: "writer"
tools: ["read"]
---
你是一名资深技术写手...
5.2 挂载目录到引擎
// 创建挂载目录定义
MountDir agentMount = MountDir.builder()
.alias("@agents") // 挂载点名称
.path("./agents") // 本地文件系统路径
.type(MountType.AGENTS) // 挂载类型
.build();
// 添加到引擎
engine.addMount(agentMount);
添加挂载后,引擎会在启动时扫描 ./agents/ 目录下的定义文件,自动解析为 AgentDefinition 并注册到 AgentManager。
5.3 运行时动态添加子代理
除了文件挂载,你还可以在运行时通过 API 动态添加子代理:
// 运行时动态创建并注册子代理
AgentDefinition dynamicAgent = AgentDefinition.builder()
.name("fact-checker")
.description("事实核查员,负责验证文章中的技术声明是否准确")
.toolsAdd("websearch")
.systemPrompt("你是一个事实核查员,负责验证文章中的技术声明是否准确...")
.build();
engine.getAgentManager().addAgent(dynamicAgent);
// 现在主 Agent 就可以调用 fact-checker 了
这种动态注册的能力,让多 Agent 系统具备了自适应扩展的可能。比如,你可以构建一个"Agent 市场",用户可以从市场中安装新的子代理,系统在运行时动态加载。
5.4 内置子代理的挂载
Harness 本身也内置了一些挂载点。你可以通过 engine.addMount() 挂载自定义目录,引擎会自动合并内置和自定义的子代理。如果自定义子代理与内置子代理名称冲突,自定义的优先级更高——这允许你覆盖默认行为。
// 查看当前所有可用子代理
List<AgentDefinition> allAgents = engine.getAgentManager().getAgents();
allAgents.forEach(def ->
System.out.println(" - " + def.getName()));
输出可能类似:
- general (built-in)
- explore (built-in)
- plan (built-in)
- bash (built-in)
- researcher (custom, from ./agents/researcher.yaml)
- writer (custom, from ./agents/writer.yaml)
- reviewer (custom, code registered)
- fact-checker (custom, dynamic)
六、总结:任务编排的最佳实践
通过"技术文章写作团队"这个场景,我们完整地走了一遍 Harness 多 Agent 协作的落地路径。最后总结几个关键认知和最佳实践:
6.1 什么时候该用多 Agent?
不是所有场景都需要多 Agent。我的经验是:如果你的任务可以一句话描述清楚、一步执行完成,单 Agent 就够了。 多 Agent 适合以下特征的任务:
- 多阶段:任务有明显的阶段划分,不同阶段需要不同的能力
- 多角色:任务需要多个专业视角协同
- 可并行:某些子任务可以同时执行,提高效率
- 需隔离:不同子任务需要独立的上下文和记忆,避免混淆
6.2 子代理设计的三个原则
原则一:心智单一
每个子代理只做一件事,把它做到极致。researcher 只负责调研,writer 只负责写作。不要在一个子代理里塞太多职责,否则你又回到了单 Agent 的老路。
原则二:契约明确
每个子代理的 systemPrompt 里写清楚输入输出格式。这不仅是给子代理看的,也是给主 Agent 看的——主 Agent 需要知道每个子代理能产出什么,才能正确编排流程。
原则三:粒度适中
子代理的粒度不要太细也不要太粗。太细(比如"写第一段"、"写第二段")会导致编排开销过大;太粗(比如"完成整篇文章")又失去了多 Agent 的意义。以"一个独立可交付的中间产物"为粒度通常比较合适。
6.3 心智与记忆的隔离
这是多 Agent 系统最容易忽视的问题。每个子代理需要:
- 独立的心智(System Prompt) — 定义角色定位和行为准则
- 独立的记忆(Session) — 只看到与自己任务相关的上下文
Harness 通过子会话机制实现了这种隔离。子代理能继承必要的主会话信息,但不会把整个历史都加载进来。这既保护了隐私(子代理不需要知道用户全部对话历史),也节省了 Token(子代理只关注自己需要的上下文)。
6.4 编排 vs 自治
最后想讨论一个设计哲学问题:主 Agent 应该严格编排流程,还是让子代理自治协作?
Harness 的设计偏向于可控编排。主 Agent 通过 task/multitask 明确指定谁做什么,流程清晰可预期。这种方式更适合工程化落地——你能追踪每一步的执行情况,能在任意步骤介入干预,能精确控制资源消耗。
但 Harness 也保留了自治的弹性。如果你希望子代理之间能互相通信、动态协作,可以通过 multitask 声明任务间的数据依赖,引擎会自动解析依赖图并决定执行顺序。对于更复杂的场景,你还可以在子代理的工具集中加入 task 工具——让子代理也能委派任务给其他子代理,形成多级委派。
这种"中央编排为主,点对点协作为辅"的混合模式,是当前多 Agent 系统落地最务实的选择。

浙公网安备 33010602011771号