乐于分享
好东西不私藏

Hermes 源码解读一:它不是聊天壳,而是 Agent 运行时

Hermes 源码解读一:它不是聊天壳,而是 Agent 运行时

全文约 2278 字,读完大约 4 分钟。

Hermes 源码解读一:它不是聊天壳,而是 Agent 运行时

这是一组 Hermes 架构源码解读系列。

我不会按目录逐行翻译源码,也不会把它写成“功能清单”。这组文章更关心一个问题:一个真实可用的 AI Agent 系统,为什么会长成现在这个样子。

整个系列会依次拆 Hermes 的入口层、任务主循环、工具治理、Prompt 边界、Provider 抽象、Gateway、多会话状态、Plugin/Middleware、Skill/Memory、Cron 自动化和安全边界。目标读者就是后端工程师、架构师,以及正在把 AI Agent 从 demo 推到工程实践里的人。

读 AI Agent 源码时,我最怕一种误判:

把它看成“聊天界面 + 工具调用”。

这样读会很快。入口在哪,模型怎么调,工具怎么注册,几小时就能过一遍。

但如果你是后端工程师或架构师,这种读法会漏掉真正有价值的部分。

我读 Hermes 0.17.0 这版源码时,第一感觉不是“它又封装了一个 ChatGPT”,而是:

Hermes 在做一个 Agent 运行时。聊天只是其中一个入口。

这个判断很重要。

因为一个聊天壳,重点是把用户消息发给模型,再把结果展示回来。

一个 Agent 运行时,重点是把模型调用、工具执行、会话状态、权限边界、插件扩展、多平台入口和长期记忆放进同一套工程秩序里。

先看系统边界,不要先看模型调用

Hermes 仓库最容易让人分心的地方,是东西很多。

你会看到 CLI、桌面端、Web 控制台、Telegram/Slack/Discord/WhatsApp 等 Gateway、ACP 适配、Cron、Skills、Plugins、Tools、Memory、SessionDB。

如果按目录逐个看,很容易变成“功能清单式阅读”。

更好的入口是先问:

这些东西最后收口到哪里?

答案基本是 AIAgent

源码里的入口层大概是这样:

CLI / Gateway / ACP / Cron / Batch
        ↓
      AIAgent
        ↓
Prompt / Provider / Tool / Context / Session / Memory
        ↓
Registry / Plugin / Skill / SQLite / Terminal Backend

这里有一个明显的架构取舍:上层入口很多,但核心运行时尽量复用同一套 Agent。

这意味着 Hermes 不是给每个平台写一套机器人逻辑。

Telegram 来一条消息,CLI 输入一行命令,Cron 到点触发一个任务,ACP 从 IDE 发起一次请求,它们最终都要走同一个“构建上下文、调用模型、执行工具、持久化结果”的核心流程。

这才是运行时的味道。

AIAgent 现在更像一个门面

run_agent.py 会发现一个有意思的变化。

AIAgent 仍然是核心类,但它已经不再把所有逻辑都塞在一个类里硬扛。

比如:

位置现在实际做什么
AIAgent.__init__()转发到 agent.agent_init.init_agent()
AIAgent.run_conversation()转发到 agent.conversation_loop.run_conversation()
工具执行继续转发到 agent.tool_executor
Prompt 组装继续转发到 agent.system_prompt
上下文压缩继续转发到 agent.conversation_compression

这说明源码正在从“巨型 Agent 类”往“运行时门面 + 子系统模块”演进。

这不是小事。

很多 Agent 项目一开始都是一个主循环文件:读消息、拼 prompt、调模型、执行工具、追加历史。功能少的时候没问题,一旦要接多 provider、多平台、多工具、多记忆、多权限,就会变成一坨很难维护的状态机。

Hermes 现在的形态更像这样:

AIAgent 保留统一入口和兼容层,真正的复杂逻辑拆到 agent/* 模块里。

对架构读者来说,这比“它支持多少模型”更值得看。

因为这说明项目已经遇到了运行时复杂度,并开始用模块边界去承接它。

入口很多,但核心只有一条主线

从源码结构看,Hermes 至少有几类入口:

入口代表文件它解决的问题
终端命令hermeshermes_cli/main.pycli.py本机交互、模型切换、工具配置、调试
消息网关gateway/run.py多平台消息接入、授权、会话路由、后台运行
IDE 协议acp_adapter/给 VS Code、Zed、JetBrains 这类环境接 Agent
定时任务cron/jobs.pycron/scheduler.py让 Agent 按计划执行自然语言任务
批处理batch_runner.py轨迹生成、训练数据、批量任务

如果你自己做过后端平台,会知道这里真正难的不是“多写几个入口”。

难的是入口多了以后,系统还能不能保持一个稳定核心。

Hermes 的做法是把入口层和 Agent 核心拆开。

Gateway 不应该自己实现一套工具循环;Cron 也不应该绕过会话、技能和 provider 逻辑单独调模型。它们都把任务交给 AIAgent

这样做的好处很直接:

新入口可以复用老能力,新能力也能自然覆盖所有入口。

比如工具审批、prompt caching、memory provider、provider fallback、session persistence,这些能力只要在 Agent 核心里做好,CLI、Gateway、Cron 都能受益。

反过来,如果每个入口各自调用模型,这些能力就会散在各处,最后变成维护灾难。

Hermes 真正处理的是长运行问题

一个 demo 级 Agent 只需要处理一次请求。

一个运行时级 Agent 要处理的是长期运行:

  • • 用户会中途打断;
  • • 工具可能卡住、失败、返回超大结果;
  • • 会话要跨天恢复;
  • • Gateway 可能同时接多个平台;
  • • provider 可能 429、401、5xx;
  • • 插件可能改写请求或阻断工具;
  • • 上下文会膨胀到模型窗口边界;
  • • 终端命令可能有破坏性;
  • • 外部工具探针可能偶发失败;
  • • 多进程会同时写 SQLite。

Hermes 源码里很多看起来“啰嗦”的逻辑,都是这些问题逼出来的。

例如 tools/registry.py 里的工具可用性检查,不是每次都傻傻探测一遍 Docker、Playwright、Modal,而是有 TTL 缓存。更细的是,它还记录最近一次成功时间:如果刚刚成功过,下一次偶发失败不会立刻把整个工具集从模型 schema 里移除。

这就是生产代码和 demo 代码的差别。

demo 会说:“检查失败就不可用。”

长运行系统会问:“这是真的不可用,还是探针抖了一下?”

一个更准确的架构图

如果把 Hermes 抽象成一张工程图,我会这样画:

入口层
  CLI / Gateway / ACP / Cron / Batch

运行时层
  AIAgent facade
  turn_context
  conversation_loop
  tool_executor
  transports

能力层
  tools registry
  provider profiles
  plugins / middleware
  skills / memory
  context compressor

状态层
  SQLite state.db
  gateway sessions.json
  cron jobs.json
  MEMORY.md / USER.md

执行边界
  terminal backends
  approval guard
  checkpoint
  sandbox / docker / ssh / modal / daytona

这张图的重点不是“模块很多”。

重点是每一层都在控制一种复杂度。

入口层控制接入复杂度。

运行时层控制对话状态复杂度。

能力层控制扩展复杂度。

状态层控制长期记忆和恢复复杂度。

执行边界控制 Agent 真正动手时的风险复杂度。

这对我们自己做 Agent 系统有什么启发

如果你正在做一个内部 Agent 平台,或者想把个人 AI 工作流做成长期系统,我觉得 Hermes 第一篇最值得带走的不是某个类名,而是这个判断:

不要一开始就把 Agent 写成一个聊天函数。

可以先把系统拆成四个问题。

问题不成熟做法更稳的做法
用户从哪里来为每个平台写一套逻辑多入口统一成标准事件
Agent 怎么跑一个函数里完成所有事运行时门面 + 子系统
能力怎么扩展到处加 if/elseregistry / plugin / provider
状态怎么保存只存聊天记录session、message、tool、cost、lineage 分层

这个思路不一定一开始就要做全。

但要提前留出边界。

否则你很快会遇到类似问题:CLI 能用,Gateway 不能用;单轮能跑,多轮崩;本地能跑,定时任务没有记忆;工具能调用,但没有审批;插件能写,但和核心耦合到一起。

Hermes 源码给我的第一个提醒就是:

真正的 Agent 工程,不是把模型接上工具。

而是把模型放进一个可持续运行的系统里。

这一篇先把系统边界立住。

接着看最核心的一段:一次用户任务进入 Hermes 后,run_conversation() 到底怎么准备上下文、调用模型、执行工具并把结果收回来。

如果你也在做 Agent 工程,可以先点个在看或收藏。这个系列会沿着源码主线继续拆主循环、工具系统、Prompt 缓存、Gateway、Session、Plugin 和安全边界。