全文约 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 至少有几类入口:
| 入口 | 代表文件 | 它解决的问题 |
|---|---|---|
| 终端命令 | hermes、hermes_cli/main.py、cli.py | 本机交互、模型切换、工具配置、调试 |
| 消息网关 | gateway/run.py | 多平台消息接入、授权、会话路由、后台运行 |
| IDE 协议 | acp_adapter/ | 给 VS Code、Zed、JetBrains 这类环境接 Agent |
| 定时任务 | cron/jobs.py、cron/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/else | registry / plugin / provider |
| 状态怎么保存 | 只存聊天记录 | session、message、tool、cost、lineage 分层 |
这个思路不一定一开始就要做全。
但要提前留出边界。
否则你很快会遇到类似问题:CLI 能用,Gateway 不能用;单轮能跑,多轮崩;本地能跑,定时任务没有记忆;工具能调用,但没有审批;插件能写,但和核心耦合到一起。
Hermes 源码给我的第一个提醒就是:
真正的 Agent 工程,不是把模型接上工具。
而是把模型放进一个可持续运行的系统里。
这一篇先把系统边界立住。
接着看最核心的一段:一次用户任务进入 Hermes 后,run_conversation() 到底怎么准备上下文、调用模型、执行工具并把结果收回来。

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