前言:最近我一直在读 Hermes Agent 的代码。本来只是想看清 Agent Loop 怎么跑,结果越往下翻,越发现它已经不是一个简单的工具调用程序:入口协议、桌面端、模型适配、工具、记忆、压缩、协作和安全都围绕同一套运行时组织起来。这篇先画完整地图,后面的专题再逐层拆开。
系列导航:这组文章按“总览 → 入口 → 运行时 → 扩展 → 状态 → 协作 → 安全”的顺序展开;第一次阅读可以从总览开始,带着具体问题也可以直接跳到专题。
- 01|Hermes Agent 架构总览:从 Agent Loop 到智能体平台
- 02|Hermes Desktop 架构:Electron 如何连接本地 Python Agent
- 03|Hermes Agent 运行时:AIAgent、模型协议与工具执行
- 04|Hermes Agent 扩展体系:Skill、Plugin 与 MCP
- 05|实战:同一个 Markdown 检查器的 Skill、Plugin 与 MCP 三种实现
- 06|Hermes Agent 的持续学习系统:Memory、Review、Skill 与 Curator
- 07|Hermes Agent 的长会话架构:Prompt Cache、压缩与 SessionDB
- 08|Hermes Agent 的多 Agent 与自动化:从 Goal 到 Kanban
- 09|Hermes Agent 安全架构:审批、沙箱、回滚与凭据隔离
我先给它一个定位:窄腰型的智能体运行平台
很多 Agent Demo 的核心只有几步:把消息发给模型,解析工具调用,执行函数,再把结果交回模型。Hermes Agent 当然也有这个循环,但真正让我感兴趣的是循环外面的东西。它需要让同一个 Agent 同时服务 CLI、TUI、桌面界面、消息平台、编辑器协议、API、定时任务和批处理;下面又要接多种模型协议、几十类能力以及可替换的运行后端。
我目前更愿意把它理解为一个多入口、单核心、多协议、可插拔能力、统一状态层的智能体运行平台。中间的 AIAgent 和 Conversation Loop 是“窄腰”:入口怎么变化、工具怎么扩展,最后都要经过这组相对稳定的协议。
flowchart TB
Entry[CLI / TUI / 桌面 / 网关 / ACP] --> Agent[AIAgent 会话运行时]
Agent --> Loop[对话循环
Conversation Loop]
Loop --> Prompt[Prompt 与 Context Engine]
Loop --> Transport[模型提供方传输层
Provider Transport]
Loop --> Toolset[Toolset 与 Tool Registry]
Agent --> State[SessionDB 与 Memory]
Transport --> Models[模型服务]
Toolset --> Ext[内置工具 / Plugin / MCP]上图里最关键的不是模块数量,而是依赖方向:入口层不自己实现一套模型循环,扩展层也不直接侵入每个入口。只要中间这层契约不乱,系统就可以同时向上增加交互方式、向下增加能力。
我的阅读基线:我遍历了官方仓库
website/docs下 402 个 Markdown/MDX 页面,并以 2026 年 8 月 21 日再次同步到的官方main提交59795c40为源码锚点。文档用于建立功能地图,默认值、状态机和失败语义则回到对应符号核验;容易变化的工具与平台数量不当作架构结论。
入口层:不同界面,共用同一个 Agent Core
源码里能看到不少入口,但它们的职责其实很一致:接收输入、解析身份与会话、装配回调、调用 AIAgent,最后把结果转换成当前界面能展示的形式。
hermes_cli/main.py与cli.py负责命令行、配置和交互式命令;ui-tui/与tui_gateway/组成终端 UI 及其 JSON-RPC 后端;gateway/run.py是长驻消息网关,负责鉴权、路由、并发和投递;acp_adapter/把 Agent 暴露给支持 ACP 的编辑器;cron/、batch_runner.py和 API 入口复用同一套执行内核。
这里先把几个很容易混在一起的名字拆开。ACP 是 Agent Client Protocol,面向“编辑器或其他 Host 如何驱动一个完整 Agent”;TUI Gateway JSON-RPC 是 Hermes 自己为 TUI、Desktop 等富客户端提供的控制协议;MCP 则位于 Agent 的另一侧,用来连接外部 Tool、Resource 和 Prompt。它们都可能使用 JSON-RPC,但消息方法、生命周期和连接对象并不是一回事。
| 入口或宿主 | 连接协议与传输 | 适配层 | 最终怎样进入 Agent |
|---|---|---|---|
| CLI | Python 进程内调用 | cli.py / hermes_cli/ | 解析命令和 Profile 后直接创建或恢复 AIAgent |
| Ink TUI | Hermes JSON-RPC 2.0 over stdio,也可走 WebSocket | tui_gateway/ | session.create、prompt.submit 等方法映射到会话运行时 |
| Electron Desktop | Hermes JSON-RPC 2.0 over WebSocket /api/ws;设置和管理功能另走 REST | hermes serve、tui_gateway/ 与 FastAPI Backend | React Renderer 提交 Prompt、接收流式事件;本机窗口和文件能力另走 Preload IPC |
| VS Code / Zed / JetBrains | ACP 的 JSON-RPC over stdio | acp_adapter/ | 把 session/prompt、ToolCall、Diff、取消和 Permission Request 桥接到 AIAgent |
| OpenAI 兼容 Web UI 或业务客户端 | HTTP;流式响应和运行事件使用 SSE | gateway/platforms/api_server.py | 把 Chat Completions、Responses 或 Run 请求转换成隔离的 Agent 执行 |
| Telegram / Discord / Slack 等 | 各平台 SDK、Webhook、轮询或长连接 | gateway/platforms/ | 先做平台鉴权、消息归一化和 Session 路由,再调用同一 Agent Core |
| Cron / Batch | 进程内调度,不需要 UI 协议 | cron/、batch_runner.py | 按 Job 或批任务建立新的执行上下文 |
所以,Electron Desktop 并不通过 ACP 连接 Hermes:它连接的是 hermes serve 暴露的 TUI Gateway WebSocket。只有当 VS Code、Zed、JetBrains 或其他 ACP Host 启动 hermes acp 时,链路才会经过 acp_adapter。两者最终都汇合到 AIAgent,复用相同的模型、工具、Skill、Memory 和 SessionDB;区别只在入口协议与界面事件如何翻译。
再换一个方向看:ACP 是“Host ↔ Agent”,MCP 是“Agent ↔ 外部能力”,Provider Transport 是“Agent ↔ 模型 API”。一个典型编辑器会话可以同时经过三层:IDE 通过 ACP 驱动 Hermes,Hermes 通过 MCP 查询工程平台,再通过 Provider Transport 请求模型推理。协议是可以叠加的,不是三选一。
平台差异没有被塞进主循环里,而是通过 callback 和 session metadata 向外释放。工具开始、工具完成、模型思考、流式增量、澄清问题、状态变化,都有各自的回调。核心只负责说“发生了什么”,至于显示成终端 Spinner、聊天消息编辑还是编辑器进度条,由入口自己决定。
flowchart LR
Input[平台原始消息] --> Normalize[统一 SessionSource]
Normalize --> Auth{鉴权与路由通过?}
Auth -->|否| Reject[拒绝或配对流程]
Auth -->|是| Session[恢复 / 创建会话]
Session --> Runtime[解析模型、工具集和 Profile]
Runtime --> Agent[智能体运行入口
AIAgent.run_conversation]
Agent --> Events[流式文本与状态回调]
Events --> Deliver[平台适配器投递]这里有个很重要的结果:CLI 和消息网关复用的不是几段工具代码,而是完整的 Agent 语义。模型回退、上下文压缩、记忆、审批和持久化都在核心层,因此换一个入口,不应该换一套“大脑”。
运行时到底由哪些组件组成
只看目录很容易把 Hermes Agent 理解成“一堆功能模块”。但真正运行时,它们并不是平铺在一起,而是被装配成一个会话级对象图:入口提供身份和回调,运行时解析模型与工具,Agent 持有当前会话状态,插件和 MCP 则叠加在注册中心与生命周期节点上。
| 组件 | 主要实现位置 | 运行时职责 | 生命周期 |
|---|---|---|---|
| 入口适配器 | cli.py、gateway/、acp_adapter/ | 接收输入、鉴权、建立平台回调、投递输出 | 进程级或连接级 |
| AIAgent | run_agent.py | 保存一个会话所需的模型、工具、记忆、预算和中断状态 | 会话级 |
| Agent Init | agent/agent_init.py | 解析配置并装配 Agent 对象图 | 创建或重建 Agent 时 |
| Conversation Loop | agent/conversation_loop.py | 组织模型调用、工具结果和最终答案 | 每个用户 Turn |
| Prompt System | agent/system_prompt.py、agent/prompt_builder.py | 构建冻结 Prompt 和本轮临时上下文 | 新会话、恢复与请求级 |
| Provider Runtime | plugins/model-providers/、agent/transports/ | 选择凭据、模型协议并归一化响应 | Agent 创建与每次 API 调用 |
| Tool Registry | tools/registry.py、model_tools.py | 保存 Schema、handler、可用性与作用域 | 进程级注册,会话级快照 |
| Tool Executor | agent/tool_executor.py | 审批、分段并发、执行并回填工具结果 | 每个工具批次 |
| Skill System | tools/skills_tool.py、agent/skill_commands.py | 提供紧凑索引与按需加载的操作知识 | 会话级索引、按调用加载 |
| Plugin Manager | hermes_cli/plugins.py | 发现插件并注册工具、Hook、Provider 和中间件 | Profile / 进程级 |
| MCP Client | tools/mcp_tool.py | 连接外部工具服务并动态注册 Schema | 服务连接级 |
| Memory / Context | tools/memory_tool.py、agent/memory_manager.py、agent/context_engine.py | 文件记忆、外部召回、上下文选择与压缩 | 会话级与 Turn 级 |
| SessionDB | hermes_state.py | 消息、Prompt、用量、搜索、租约与并发协调 | 持久化 |
flowchart TB
Config[Profile 配置与凭据] --> Init[agent_init 装配]
Plugins[Plugin Manager 注册覆盖层] --> Init
MCP[MCP 动态工具] --> Registry[工具注册表
Tool Registry]
Init --> Agent[AIAgent 会话对象]
Init --> Transport[模型提供方传输层
Provider Transport]
Init --> Registry
Init --> Memory[记忆管理器
Memory Manager]
Init --> Context[上下文引擎
Context Engine]
Agent --> Loop[对话循环
Conversation Loop]
Loop --> Transport
Loop --> Registry
Loop --> Memory
Loop --> Context启动时和每轮请求时,不是同一套工作
我读代码时比较容易混淆“注册时发生什么”和“对话时发生什么”。把它们分开后就清楚了:
- 进程启动或 Profile 首次使用:发现内置工具和插件,注册 Provider、平台、Hook 与工具元数据;MCP 连接后也会把远端工具加入 Registry。
- 创建 AIAgent:解析模型、API mode、凭据、Toolset、Memory Provider 和 Context Engine,取得当前 Registry 的会话快照。
- 开始一个 Turn:获取会话租约、加载历史、组装本轮上下文、调用模型、执行工具并最终提交。
- Turn 结束:持久化消息与用量,触发观察 Hook、记忆异步同步和 Context Engine 的完成回调,再释放租约。
因此 Plugin 和 MCP 并不是每轮临时“插进来”的独立旁路。多数情况下,它们在更早阶段把能力注册到现有边界,到了 Conversation Loop 里看起来就和内置能力差不多,只是多了来源、作用域和授权信息。
从全站功能回看它的亮点
通读文档站后,我觉得项目亮点并不是“内置了很多工具”。更有意思的是,它把个人 Agent 运行几年后一定会遇到的六类问题都做成了明确边界:入口、学习、持续执行、扩展、安全和模型运营。很多功能看起来相距很远,源码里却共享同一套 Session、Profile、Toolset 与生命周期契约。
| 亮点 | 用户能看到的功能 | 源码中的共同机制 |
|---|---|---|
| 闭环学习 | Memory、Session Search、Agent-created Skills、Curator、Learning Journey | MemoryStore、background_review.py、skill_manage、FTS5 与写入审批 |
| 多入口同语义 | CLI、TUI、Desktop、20+ 消息入口、API Server、ACP | AIAgent、Gateway Session、JSON-RPC、Platform Registry 与统一回调 |
| 持续执行梯度 | Loops、Heartbeat、Goals、Cron、Delegation、Kanban、Bot Mode | 从会话内定时器、Goal contract,到持久 Job/Board 与 Profile Worker,不拿一种机制硬撑所有生命周期 |
| 扩展不撑大核心 | Skills、Plugin、MCP、Provider、Memory Provider、Context Engine | Tool Registry 窄腰、配置选择、动态 Toolset 与显式兼容契约 |
| 副作用有多层安全网 | 危险命令审批、文件检查点与 rollback、Worktree、Sandbox、Secrets、Managed Scope、Egress Proxy | 审批管线、shadow Git snapshot、Environment Backend、Secret Source 与出站凭据注入 |
| 模型运营不是硬编码 | Provider Routing、Fallback、Credential Pool、Auxiliary Models、MoA | Provider Profile、Transport、运行时凭据解析、任务级路由与统一 usage 记录 |
尤其值得注意的是“持续执行梯度”。/loop 适合在当前会话重复一个 Prompt,Heartbeat 在会话空闲时重新进入,Goal 用完成条件推动跨 Turn 工作,顶层 Delegation 并行但仍是进程内临时任务,Cron 把计划持久化为定时 Job,Kanban 再增加依赖、认领、Review 和崩溃恢复。架构上先问生命周期,再选机制,比笼统地说“支持 Autonomous Agent”更有解释力。
做得好的地方
- 多入口复用的是语义。不同界面天然得到同一套模型、工具、记忆、压缩和持久化行为。
- 供应商差异被限制在协议边界。上层面对统一消息模型,不需要跟每一家 API 一起变化。
- 扩展方式有重量级。Skill、Plugin、MCP、Provider 和核心工具各有位置。
- 长会话不是事后补丁。Prompt Cache、sidecar、Context Engine 和 lease 从不同角度保护连续性。
- Profile 作用域进入了注册中心。一个进程可以同时服务相互隔离的配置与扩展。
它付出的代价
run_agent.py、conversation_loop.py和gateway/run.py仍然很大,历史兼容逻辑的阅读成本不低;- 动态注册让最终能力集依赖配置、Profile、加载顺序和外部探测,静态分析更难;
- Prompt Cache 的不变量限制了会话中途随意修改系统提示和工具集;
- Agent Cache、SQLite、路由状态、压缩锁和 turn lease 共同工作,多进程一致性本身已经是一个子系统;
- Hook 和 Provider 一旦开放,就会产生长期兼容与安全治理成本。
所以它不是那种“代码很少、边界特别漂亮”的教学框架。它更像一套高速演进、已经承受真实入口和真实状态压力的系统。架构上的价值,恰恰在于这些复杂性大多有明确归属,而不是完全散落在每条调用链里。
如果只记五件事
- 把 Prompt Cache 当成架构不变量。明确哪些内容会话内冻结,哪些只按轮注入。
- 控制模型可见接口的面积。能用现有工具、Skill、Plugin 或 MCP 解决,就不要急着加核心工具。
- 能力属于 Session,不属于 Process。权限和工具可用性必须根据当前会话来源解析。
- 分开历史、记忆和上下文选择。它们解决的问题不同,也应该有不同生命周期。
- 为一轮执行建立所有权。成熟 Agent Turn 需要预算、租约、中断、审批和 Finalizer,不能只依赖同步调用栈。
一开始我以为 Hermes Agent 最值得看的会是某个 Prompt 或某个工具。代码翻完后,我反而觉得它最有价值的地方是:当 Agent 从 Demo 变成长时间运行的软件,重点会从“怎么让模型行动”变成“怎么约束行动、保存状态、控制成本,并让系统继续扩展而不把核心撑爆”。
参考资料与源码入口
题图来源:《山、鸟类与轮廓》,创作者 giani,来自 Pixabay;依据 Pixabay Content License(Pixabay 内容许可)使用。