当单个 Agent 的能力触及天花板,真正的突破不在于更强的模型,而在于更优雅的协作。AgentScope Harness 用"文件即规格、状态即协调"的设计,让多 Agent 编排从代码硬编码走向声明式配置。
一、引言:为什么单 Agent 不够用?
在实际业务中,我们很快会遇到单 Agent 的三重瓶颈:
| 瓶颈 | 表现 |
|---|---|
| 能力边界 | 一个 Agent 无法同时精通代码审查、数据分析、文档撰写 |
| 上下文膨胀 | 所有工具和知识塞进一个 prompt,token 爆炸、注意力分散 |
| 职责混乱 | “全能 Agent” 变成"什么都做但什么都不精"的万金油 |
| 传统的多 Agent 框架通常用代码硬编码来解决这个问题: |
// 传统方式:编排逻辑写在代码里if(task.equals("code_review")){codeReviewAgent.call(input);}elseif(task.equals("data_analysis")){dataAnalysisAgent.call(input);}这种方式的问题显而易见:**每新增一个子 Agent,都要改代码、重新编译、重新部署。**编排逻辑和业务逻辑耦合,非技术人员无法参与,版本管理困难。
AgentScope Java 2.0 的 Harness 模块给出了一个截然不同的答案:
子 Agent 的规格声明是文件,不是代码。主 Agent 通过读取工作区中的 Markdown 文件来发现和编排子 Agent。
二、核心设计哲学:文件驱动编排
2.1 三大原则
┌─────────────────────────────────────────────────────────────┐ │ 文件驱动编排的三大原则 │ │ │ │ 1. 规格即文件 子 Agent 的能力描述是 Markdown,不是 Java 类 │ │ 2. 发现即扫描 框架自动扫描 subagents/ 目录,无需手动注册 │ │ 3. 编排即推理 主 Agent 通过 LLM 推理决定委派,不是 if-else │ └─────────────────────────────────────────────────────────────┘2.2 与传统方式的对比
| 维度 | 代码硬编码编排 | Harness 文件驱动编排 |
|---|---|---|
| 新增子 Agent | 改代码 + 编译 + 部署 | 放一个 .md 文件到 subagents/ |
| 非技术人员参与 | ❌ 不可能 | ✅ 编辑 Markdown 即可 |
| 版本管理 | Git commit 散落 | 整个 subagents/ 目录天然 Git 友好 |
| 运行时动态调整 | ❌ 需重启 | ✅ 下轮推理自动生效 |
| 多环境迁移 | 代码分支 / 配置中心 | 复制目录 |
| 人机共编 | ❌ | ✅ 开发者、PM、领域专家都能编辑 |
三、子 Agent 规格文件(Subagent Spec)
3.1 文件位置与命名约定
workspace/ └── subagents/ ├── weather-agent.md ← 天气查询子 Agent ├── flight-agent.md ← 航班搜索子 Agent ├── code-reviewer.md ← 代码审查子 Agent └──>3.2 规格文件格式每个 .md 文件遵循统一的 Front Matter + Body 结构:
--- name: weather-agent description: 查询指定城市的实时天气和未来天气预报 tools: - get_weather - get_forecast model: gpt-4o-mini # 可选:指定子 Agent 使用的模型 max_turns: 5 # 可选:最大推理轮次 timeout_seconds: 30 # 可选:超时时间 --- # Weather Agent ## 职责 你是一个专业的天气查询助手。当用户询问天气相关问题时, 使用 get_weather 和 get_forecast 工具获取准确数据。 ## 输出格式 - 当前天气:温度、湿度、风力、天气状况 - 未来预报:按天列出,包含最高/最低温度和降水概率 ## 约束 - 只回答天气相关问题,其他问题礼貌拒绝 - 数据来源必须是工具返回的结果,不要编造 - 温度单位默认摄氏度,用户指定华氏度时切换
3.3 Front Matter 字段详解
字段 必填 类型 说明 name ✅ String 子 Agent 唯一标识,用于委派调用 description ✅ String 能力描述,注入主 Agent system prompt tools ❌ List 子 Agent 可用的工具白名单 model ❌ String 指定模型,不填则继承主 Agent 模型 max_turns ❌ Integer 最大 ReAct 推理轮次 timeout_seconds ❌ Integer 单次执行超时(秒) sandbox ❌ Object 沙箱配置(隔离执行) memory ❌ Object 记忆配置(独立记忆空间)
3.4 Body 部分的作用
Body 部分是子 Agent 的完整 system prompt。它定义了:
- 角色定位和行为约定
- 输入输出格式规范
- 约束和禁止行为
- 领域知识和工作流指引
💡 关键设计:Front Matter 是机器可读的结构化元数据,Body 是人类可读的自然语言指令。两者分离,各司其职。
四、自动发现与装配机制
4.1 构建期扫描
HarnessAgentmainAgent=HarnessAgent.builder().name("travel-assistant").model(newOpenAIChatModel(apiKey,"gpt-4o")).workspace(Path.of("./workspace"))// 框架自动扫描 workspace/subagents/*.md// 无需手动注册任何子 Agent.build();
扫描流程:
HarnessAgent.build() │ ▼ WorkspaceContextHook │ ├── listDir("subagents/") │ → [weather-agent.md, flight-agent.md, ...] │ ├── 逐个解析 Front Matter │ → SubagentSpec(name, description, tools, ...) │ ├── 生成委派工具描述 │ → "delegate_to_weather_agent: 查询指定城市的实时天气..." │ └── 注入主 Agent system prompt → 主 Agent "知道"自己有哪些子 Agent 可以委派
4.2 注入主 Agent 的内容
框架将每个子 Agent 的 name + description 拼装为一段委派指引,注入主 Agent 的 system prompt:
## Available Sub-Agents You can delegate tasks to the following specialized sub-agents: - **weather-agent**: 查询指定城市的实时天气和未来天气预报 - **flight-agent**: 搜索和比较航班信息,支持多条件筛选 - **code-reviewer**: 审查代码质量、安全性和最佳实践 - **data-analyst**: 分析数据集,生成图表和洞察报告 Use the `delegate_to_<agent_name>` tool to assign tasks. Only delegate when the task clearly matches a sub-agent's capability.
4.3 运行时热更新
由于子 Agent 规格是从文件实时读取的,修改 .md 文件后,下一轮推理立即生效:
# 新增一个子 Agentecho"--- name: hotel-agent description: 搜索和推荐酒店,支持价格/星级/位置筛选 tools: - search_hotels - get_hotel_details --- # Hotel Agent ...">workspace/subagents/hotel-agent.md# 无需重启服务,下一次 call() 自动发现 hotel-agent
五、委派执行流程
5.1 完整调用链
用户:"帮我查一下明天北京到上海的航班,然后看看上海天气" │ ▼ 主 Agent 推理 │ 识别出两个子任务:航班查询 + 天气查询 │ ├── ① delegate_to_flight_agent("明天北京到上海的航班") │ │ │ ▼ 框架创建子 Agent 实例 │ │ 加载 flight-agent.md 的完整 spec │ │ 装配 tools 白名单中的工具 │ │ 设置 model / max_turns / timeout │ │ │ ▼ 子 Agent ReAct 推理 │ │ 调用 search_flights 工具 │ │ 生成结构化结果 │ │ │ ▼ 返回结果给主 Agent │ ├── ② delegate_to_weather_agent("上海明天的天气") │ │ │ ▼ 同上流程 │ │ │ ▼ 返回结果给主 Agent │ ▼ 主 Agent 整合两个子任务的结果 │ 生成统一的回复 │ ▼ 返回给用户
5.2 委派工具的内部实现
delegate_to_<agent_name> 是一个由框架自动注册的内部工具,对主 Agent 来说和普通工具没有区别:
{"name":"delegate_to_weather_agent","description":"查询指定城市的实时天气和未来天气预报","parameters":{"type":"object","properties":{"task":{"type":"string","description":"要委派给 weather-agent 的具体任务描述"}},"required":["task"]}}
5.3 子 Agent 的执行隔离
每个子 Agent 在执行时拥有独立的上下文:
维度 主 Agent 子 Agent System Prompt AGENTS.md + MEMORY.md + 子 Agent 列表 子 Agent spec body 工具集 全部工具 + 委派工具 仅 spec 中声明的 tools 对话历史 完整用户对话 仅本次委派的 task 描述 记忆 共享 MEMORY.md 可配置独立记忆空间 沙箱 主 Agent 沙箱 可配置独立沙箱
💡 设计意图:子 Agent 不需要也不应该看到主 Agent 的完整上下文。这既节省了 token,又避免了信息泄露和注意力分散。
六、高级编排模式
6.1 串行编排(Pipeline)
用户请求 → 主 Agent │ ├── delegate_to_data_collector("收集Q3销售数据") │ ↓ 返回原始数据 ├── delegate_to_data_analyst("分析Q3销售趋势") │ ↓ 返回分析报告 └── delegate_to_report_writer("生成Q3销售报告") ↓ 返回最终报告
主 Agent 通过 LLM 推理自动决定串行顺序,无需代码定义 pipeline。
6.2 并行编排(Fan-out / Fan-in)
用户请求 → 主 Agent │ ├── delegate_to_flight_agent(...) ─┐ ├── delegate_to_hotel_agent(...) ─┼── 并行执行 └── delegate_to_weather_agent(...) ─┘ ↓ 主 Agent 整合三个结果
⚠️ 注意:并行执行取决于主 Agent 的推理能力和模型的 function calling 支持。部分模型支持在一次响应中调用多个工具。
6.3 条件编排(Conditional)
用户请求 → 主 Agent │ ├── 判断任务类型 │ ├── 代码相关 → delegate_to_code_reviewer │ ├── 数据相关 → delegate_to_data_analyst │ └── 通用问题 → 自己回答 │ ▼ 根据 LLM 推理结果动态选择
**关键点:**条件判断由 LLM 推理完成,不是代码中的 if-else。新增分支只需添加新的子 Agent 文件。
6.4 嵌套编排(Hierarchical)
主 Agent ├── delegate_to_research_agent("调研竞品") │ ├── delegate_to_web_searcher("搜索竞品信息") │ └── delegate_to_doc_reader("阅读竞品文档") └── delegate_to_report_writer("生成竞品分析报告")
子 Agent 本身也可以是 HarnessAgent,拥有自己的 subagents/ 目录,形成多级编排树。
七、子 Agent 与工作区的深度集成
7.1 共享工作区 vs 独立工作区
// 方式一:共享主 Agent 工作区(默认)// 子 Agent 可以读取主 Agent 的 knowledge/、skills/ 等// 方式二:独立工作区HarnessAgentsubAgent=HarnessAgent.builder().name("isolated-analyst").workspace(Path.of("./workspaces/analyst"))// 独立目录.build();
模式 适用场景 优势 风险 共享工作区 子 Agent 需要访问主 Agent 的知识/技能 资源共享,避免重复 子 Agent 可能误改主 Agent 文件 独立工作区 子 Agent 完全自治 强隔离,互不干扰 需要单独维护知识和配置
7.2 任务记录持久化
子 Agent 的执行记录自动写入工作区:
workspace/agents/<mainAgentId>/tasks/ ├── sess-001.json ← 会话级任务汇总 └── sess-001/ ├── task-001-flight.json ← 航班查询任务详情 └── task-002-weather.json ← 天气查询任务详情
每个任务记录包含:
- 委派的 task 描述
- 子 Agent 的完整推理过程
- 工具调用日志
- 最终返回结果
- 耗时和 token 消耗
7.3 记忆联动
编辑# subagents/data-analyst.md Front Mattermemory:enabled:truescope:independent# independent | sharedflush_trigger:always
scope 行为 shared 子 Agent 读写主 Agent 的 MEMORY.md independent 子 Agent 拥有独立的 MEMORY.md 和 memory/ 目录
八、完整配置示例
8.1 差旅助手主 Agent
HarnessAgenttravelAssistant=HarnessAgent.builder().name("travel-assistant").model(newOpenAIChatModel(apiKey,"gpt-4o")).workspace(Path.of("./workspace"))// 记忆.memory(MemoryConfig.defaults())// 压缩.compaction(CompactionConfig.builder().triggerMessages(30).keepMessages(10).build())// 沙箱.filesystem(newSandboxFilesystemSpec().backend(newDockerSandboxBackend().image("python:3.11-slim").build()).isolationScope(IsolationScope.USER).build())// 状态持久化.stateStore(newRedisAgentStateStore(redisClient))// 子 Agent 自动从 workspace/subagents/ 发现// 无需额外配置!.build();
8.2 对应的工作区结构
workspace/ ├── AGENTS.md │ # Travel Assistant │ ## 角色 │ 你是一个企业差旅助手,负责协调航班、酒店、天气等信息查询。 │ ## 编排策略 │ - 航班查询委派给 flight-agent │ - 酒店推荐委派给 hotel-agent │ - 天气查询委派给 weather-agent │ - 简单问题直接回答,不要过度委派 │ ├── MEMORY.md ├── tools.json ├── knowledge/ │ └── reimbursement-policy.md │ ├── subagents/ │ ├── flight-agent.md │ ├── hotel-agent.md │ ├── weather-agent.md │ └── expense-calculator.md │ └── agents/travel-assistant/ ├── sessions/ └── tasks/
九、与其他子系统的协作
┌─────────────────────────────────────────────────────────────┐ │ 子 Agent 编排生态 │ │ │ │ ┌──────────────┐ │ │ │ subagents/*.md│ ← 规格文件(人类编辑 / Agent 自生成) │ │ └──────┬───────┘ │ │ │ 构建期扫描 │ │ ▼ │ │ ┌──────────────┐ 注入 system prompt │ │ │ 主 Agent │ ◀────────────────────────────────────┐ │ │ │ (HarnessAgent)│ │ │ │ └──────┬───────┘ │ │ │ │ delegate_to_xxx │ │ │ ▼ │ │ │ ┌──────────────┐ │ │ │ │ 子 Agent │ ── 任务记录 ──▶ tasks/*.json │ │ │ │ (HarnessAgent)│ │ │ │ └──────┬───────┘ │ │ │ │ │ │ │ ┌────┴────┬──────────┬──────────┐ │ │ │ ▼ ▼ ▼ ▼ │ │ │ ┌──────┐ ┌──────┐ ┌────────┐ ┌────────┐ │ │ │ │Tools │ │Memory│ │Sandbox │ │Session │ │ │ │ │白名单│ │独立/ │ │独立/ │ │持久化 │ │ │ │ │ │ │共享 │ │共享 │ │ │ │ │ │ └──────┘ └──────┘ └────────┘ └────────┘ │ │ │ │ │ │ 主 Agent system reminder ◀── 任务状态反馈 ─────────────┘ │ └─────────────────────────────────────────────────────────────┘
子系统 与子 Agent 的关系 Workspace 规格文件存储在工作区,任务记录写入工作区 记忆 可配置独立或共享记忆空间 沙箱 可配置独立沙箱或共享主 Agent 沙箱 压缩 子 Agent 有独立的上下文窗口和压缩策略 权限 工具白名单在 spec 中声明,框架强制执行 Session 子 Agent 的推理过程持久化为任务记录
十、最佳实践
10.1 规格文件编写指南
原则 说明 description 要精确 这是主 Agent 决定委派的唯一依据,模糊描述导致误委派 Body 要自包含 子 Agent 看不到主 Agent 的上下文,spec body 必须包含所有必要信息 tools 要最小化 只授予完成任务必需的工具,遵循最小权限原则 约束要明确 明确写出"不做什么"比"做什么"更重要 输出格式要标准化 便于主 Agent 解析和整合
10.2 编排策略建议
场景 推荐策略 任务明确、边界清晰 文件驱动委派(本文方案) 需要复杂条件分支 主 Agent LLM 推理 + 子 Agent 文件 需要严格顺序保证 在主 Agent AGENTS.md 中写明编排流程 子 Agent 间需要通信 通过主 Agent 中转,不要让子 Agent 直接对话 动态生成子 Agent Agent 自己写 .md 文件到 subagents/,下轮生效
10.3 常见反模式
# ❌ description 太模糊 description: 处理各种任务 # ❌ Body 依赖主 Agent 上下文 ## 注意事项 请参考上面用户提到的报销标准... # 子 Agent 看不到"上面" # ❌ tools 过多 tools: [tool_a, tool_b, tool_c, ..., tool_z] # 违反最小权限 # ❌ 没有约束 # 缺少"禁止行为"段落,子 Agent 可能越界
十一、设计哲学总结
1. 规格即文件,编排即推理
这是整个子 Agent 系统的基石。规格不是代码,编排不是 if-else。这使得非技术人员可以参与 Agent 能力建设,也使得系统可以在运行时自我进化。
2. 发现优于注册
框架自动扫描 subagents/ 目录,无需手动注册。新增子 Agent 的唯一操作就是放一个文件。这种"约定优于配置"的设计大幅降低了使用门槛。
3. 隔离优于共享
子 Agent 默认拥有独立的上下文、工具集和对话历史。共享是显式配置的例外,不是默认行为。这确保了子 Agent 的专注性和安全性。
4. 记录优于遗忘
每次委派的完整过程都持久化为任务记录。这不仅支持事后审计,也为主 Agent 提供了"反思"的素材——它可以回顾过去的委派效果,优化未来的编排决策。
5. 组合优于继承
子 Agent 不是主 Agent 的子类,而是独立的、可组合的能力单元。同一个子 Agent 可以被多个主 Agent 复用,也可以在嵌套编排中被其他子 Agent 调用。
十二、结语
AgentScope Harness 的子 Agent 编排设计,回答了一个根本问题:
如何让多 Agent 协作像搭积木一样灵活,而不是像写代码一样僵硬?
答案是:把编排的"知识"从代码中解放出来,变成人类可读、机器可解析、Agent 可自生成的文件。
当你把子 Agent 的规格看作"文档"而非"配置"时,很多设计决策就变得自然而然了:
- 文档可以 Git 管理 → 版本控制免费获得
- 文档可以由任何人编辑 → 团队协作门槛降低
- 文档可以在运行时更新 → 热更新零成本
- 文档可以由 Agent 自己撰写 → 自我进化成为可能
如果你正在构建需要多能力协作的 Agent 系统,这套"文件驱动编排"的设计思路值得深入研究和借鉴。它不仅仅是一种技术方案,更是一种让 AI 系统回归人类可理解、可参与、可治理的工程哲学。