系列:Hermes Agent 源码探秘 作者:元思未来 字数:约3000字


前面七篇我们聊的都是"单个 Agent"怎么工作。但现实中,复杂任务光靠一个人是不够的

你想一下:

  • 如果一个任务需要同时做三件事(查资料、写代码、测试),一个 Agent 只能串行做
  • 如果一个子任务需要完全隔离的环境(不同的工作目录、不同的工具集)
  • 如果你想让 Agent 长时间在后台运行,同时你继续做别的事

这些场景,就需要子代理(Sub-Agent) 机制。


一、问题分析:为什么需要子代理?

场景一:并行工作

用户需求:帮我对比一下 Go 和 Rust,写一份报告

没有子代理(串行):
  1. 搜索 Go 的资料
  2. 搜索 Rust 的资料(等上面完了才开始)
  3. 对比分析
  4. 写报告
  总耗时:1 号+2 号+3 号+4 号

有子代理(并行):
  子代理A → 搜索 Go 资料 ──┐
                          ├── 同时进行
  子代理B → 搜索 Rust 资料 ──┘
  主Agent等待两个都完成后 → 对比分析 → 写报告
  总耗时:max(1号, 2号) + 3号 + 4号

场景二:环境隔离

场景:Agent 需要读取生产环境的日志分析问题

主Agent:运行在项目开发目录
子AgentA:SSH 到生产服务器分析日志
子AgentB:在另一个目录写修复代码

三个 Agent 互不干扰,各自有独立的文件系统和工具集

场景三:长时间任务

场景:Agent 有一个需要跑 30 分钟的任务

主Agent:继续跟用户聊天、处理其他请求
后台子Agent:跑那个耗时任务,完成后汇报结果

二、Hermes 的子代理机制

Hermes 通过 delegate_task 工具实现子代理。

使用方式

在对话中,Agent 可以调用 delegate_task 工具:

# tools/delegate_tool.py (简化)

def delegate_task(goal: str, context: str = "", 
                  toolsets: list = None, tasks: list = None,
                  task_id: str = None) -> str:
    """将任务委派给子代理"""
    
    # 1. 创建子 Agent
    child = AIAgent(
        model=parent_model,      # 可以用和父级相同的模型
        toolsets=toolsets,       # 只给子代理需要的工具
        max_iterations=50,       # 子代理有自己的迭代限制
        platform="delegation",   # 标记来源
        ...
    )
    
    # 2. 子代理独立运行
    result = child.run_conversation(
        context + "\n\n任务:" + goal
    )
    
    # 3. 返回摘要
    return json.dumps({
        "summary": result["final_response"],
        "status": "completed"
    })

并发模式

Hermes 支持并发派发多个子代理:

# 并发派发 3 个子代理
tasks = [
    {"goal": "搜索 Go 语言的优势和劣势", "toolsets": ["web"]},
    {"goal": "搜索 Rust 语言的优势和劣势", "toolsets": ["web"]},
    {"goal": "查找 Go 和 Rust 的性能对比数据", "toolsets": ["web"]},
]

result = delegate_task(tasks=tasks)  
# 注意:tasks 是数组,3 个子代理会并发执行

delegate_task 接收到 tasks 数组时,会创建多个子代理并行执行:

if tasks:
    # 并行模式:每个 task 一个子代理
    with ThreadPoolExecutor(max_workers=len(tasks)) as executor:
        futures = []
        for task in tasks:
            future = executor.submit(
                _run_child_agent,
                goal=task["goal"],
                toolsets=task.get("toolsets", default_toolsets)
            )
            futures.append(future)
        
        results = [f.result() for f in futures]
    return json.dumps({"results": results})

三、子代理的隔离机制

子代理不是"瘦客户端",而是完全独立的 Agent 实例

主 Agent
  ├── 上下文:当前对话
  ├── 工作目录:/project/main
  ├── 工具集:所有
  └── 迭代限制:90
       │
       ├── 子代理 A
       │     ├── 上下文:仅有委派时传递的内容
       │     ├── 工作目录:独立临时目录
       │     ├── 工具集:web(只有搜索)
       │     └── 迭代限制:50
       │
       ├── 子代理 B
       │     ├── 上下文:仅有委派时传递的内容
       │     ├── 工作目录:独立临时目录
       │     ├── 工具集:terminal, file
       │     └── 迭代限制:50
       │
       └── 子代理 C
             ├── 上下文:仅有委派时传递的内容
             ├── 工作目录:独立临时目录
             ├── 工具集:web
             └── 迭代限制:50

隔离的内容包括:

维度是否隔离说明
对话上下文✅ 完全隔离子代理看不到主 Agent 的对话历史
工作目录✅ 完全隔离子代理在临时目录操作,不影响主目录
工具集✅ 可按需配置主 Agent 可以限制子代理只能用哪些工具
LLM 模型✅ 可独立配置子代理可以用不同的模型(更快/更便宜的)
终端会话✅ 完全隔离子代理的 shell 环境独立
迭代限制✅ 独立子代理有自己的 max_iterations

四、信息流:父 Agent 怎么"知道"子 Agent 的结果?

子代理执行完后,不会把完整的对话历史返回给父 Agent——那样会浪费大量 token。它只返回一个摘要

def _run_child_agent(goal, toolsets, context=""):
    # 创建子 Agent
    child = AIAgent(toolsets=toolsets, ...)
    
    # 运行
    result = child.run_conversation(context + "\n\n" + goal)
    
    # 返回摘要
    return {
        "goal": goal,
        "status": "completed",
        "summary": result["final_response"],  # 只返回最终回复
        # 注:不返回完整对话历史!
    }

父 Agent 拿到摘要后,可以根据需要决定下一步:

# 父 Agent 的视角
子代理A的摘要:"Go语言的优势...劣势..."
子代理B的摘要:"Rust语言的优势...劣势..."
子代理C的摘要:"性能对比:Go在并发方面占优..."

父 Agent:(看了三个摘要后)
"好,我来整合成一份完整的对比报告..."

五、子代理 vs 其他并发机制

Hermes 有多种"并行/后台"机制,它们各有用处:

机制同步/异步隔离度适用场景
delegate_task同步等待高(全隔离)需要结果的并行子任务
terminal(background)异步中(仅终端隔离)启动服务器、跑长任务
cronjob定时触发高(完整会话)定时任务、每日报告
spawn hermes独立进程最高(完全独立)长时间独立任务

选择建议:

任务需要结果才能继续?       → delegate_task
任务在后台跑,不关心结果?     → terminal(background)
任务需要定时执行?            → cronjob
任务需要完全独立运行几天?     → spawn hermes process

六、一个完整的委派案例

为了展示子代理的威力,看一个真实场景:

用户需求: "帮我调研三个 RAG 框架,整理对比报告"

用户输入
    │
    ▼
主 Agent 分析:需要调研 3 个框架,可以并行
    │
    ▼
派发 3 个子代理(并行):
  ├── 子代理 A: "调研 LangChain 的 RAG 实现"
  │     └── 独立搜索、独立分析
  ├── 子代理 B: "调研 LlamaIndex 的 RAG 实现"
  │     └── 独立搜索、独立分析
  └── 子代理 C: "调研 Chroma + 原生 RAG 实现"
        └── 独立搜索、独立分析
    │
    ▼
主 Agent 等待 3 个子代理完成
    │
    ▼
收到三个摘要:
  A: "LangChain 支持... 优点... 缺点..."
  B: "LlamaIndex 支持... 优点... 缺点..."
  C: "原生 RAG 实现方式..."
    │
    ▼
主 Agent 整合、对比、撰写报告
    │
    ▼
输出完整的对比报告给用户

总耗时:max(3个子代理耗时) + 整合耗时
        而不是 3个子代理耗时之和

这就是子代理的价值:把串行变并行,把复杂变分解。


七、下一篇预告

从第1篇的技术原理,到第8篇的子代理机制。我们已经把一个 AI Agent 的里里外外都拆了一遍。

第九篇我来点实战内容——讲我实际用 Hermes 做什么

作为全栈老程序员,我用 Hermes 来:

  • 帮我写自媒体文章的初稿
  • 辅助开发 Python 小工具
  • 设置定时任务(每日信息整理)
  • 把我的经验整理成"技能"

这些都是真实的使用案例,不是空谈理论。下篇见。


代码位置: ~/.hermes/hermes-agent/tools/delegate_tool.py
并发限制: 默认最多 3 个子代理并行(可配置)
注意: 子代理是同步等待的(主Agent会阻塞直到子代理完成)


元思未来 · 行稳致远,进而有为