仓库地图:五层架构速览
本章核心源码:
hermes_cli/main.py、run_agent.py、model_tools.py、hermes_state.py、gateway/run.py
定位:本章建立源码空间感,将 hermes-agent 仓库按五层划分,标注每层关键文件与职责。 前置依赖:无。适用场景:初次接触 Hermes 源码,需要快速建立全局地图。
为什么需要一张地图
Hermes Agent 的代码库不小:十余个顶层 Python 文件、二十多个 agent 核心模块、数十个工具实现(含子目录)、17 个 Gateway 平台类型、数十个 CLI 模块、400+ 个测试文件。如果按目录树从上到下浏览,你会迷失在细节里。
但这些代码不是杂乱堆砌的。它们按照一个清晰的分层模型组织:入口层、编排层、能力层、状态层、平台层。每一层有明确的职责边界,层与层之间的依赖方向是单向的——上层依赖下层,下层不感知上层的存在。
理解这张地图,后续每一章你都知道自己在哪个位置。
五层架构总览
graph TB
subgraph "入口层 — 用户如何触达 Agent"
CLI["hermes_cli/main.py<br/>CLI 命令入口"]
TUI["cli.py (8736行)<br/>交互式 TUI"]
GW["gateway/run.py<br/>消息平台网关"]
BATCH["batch_runner.py<br/>批量轨迹生成"]
CRON["cron/scheduler.py<br/>定时调度"]
end
subgraph "编排层 — Agent 的大脑"
AGENT["run_agent.py (9431行)<br/>AIAgent 核心编排器"]
TOOLS["model_tools.py<br/>工具发现与调度"]
AGENT_MOD["agent/ (25个模块)<br/>编排支撑"]
end
subgraph "能力层 — Agent 能做什么"
REGISTRY["tools/registry.py<br/>工具注册表"]
TOOL_IMPL["tools/ (69个 .py, 含子目录)<br/>工具实现"]
SKILLS["skills/ (26类)<br/>过程性知识"]
MCP["tools/mcp_tool.py<br/>MCP 外部能力"]
PLUGINS["plugins/<br/>插件系统"]
end
subgraph "状态层 — Agent 记住什么"
STATE["hermes_state.py<br/>SessionDB (SQLite)"]
MEM_MGR["agent/memory_manager.py<br/>记忆编排"]
MEM_PROV["agent/memory_provider.py<br/>记忆抽象"]
COMPRESS["agent/context_compressor.py<br/>上下文压缩"]
end
subgraph "平台层 — Agent 如何到达用户"
PLATFORMS["gateway/platforms/ (17个)<br/>消息平台适配"]
SESSION["gateway/session.py<br/>会话管理"]
ACP["acp_adapter/<br/>Agent Client Protocol"]
end
CLI --> AGENT
TUI --> AGENT
GW --> AGENT
BATCH --> AGENT
CRON --> AGENT
AGENT --> TOOLS
AGENT --> AGENT_MOD
TOOLS --> REGISTRY
REGISTRY --> TOOL_IMPL
AGENT --> MEM_MGR
AGENT --> COMPRESS
MEM_MGR --> MEM_PROV
MEM_MGR --> STATE
GW --> PLATFORMS
GW --> SESSION
style AGENT fill:#f96,stroke:#333,stroke-width:3px
图中高亮的
run_agent.py(9431 行)是整个系统的心脏,几乎所有入口最终都调用到它。
设计选择:为什么是五层而不是其他分法
在分析 Hermes 的代码组织之前,值得先说明这个五层模型的来源和选择理由。
Hermes 没有在代码中显式声明分层——不存在一个 layers.py 或分层配置文件。五层划分是从代码的依赖方向和职责边界中自然浮现的:
- 为什么不是三层?(表现层/业务层/数据层)因为 Hermes 的"表现层"实际上包含了两个截然不同的关注点:CLI 交互和消息平台适配。它们的代码量、复杂度和抽象模式完全不同,硬塞进一层会掩盖结构。
- 为什么不是洋葱架构? Hermes 不是一个 Web 服务——它没有 HTTP 请求/响应的生命周期。它的核心循环是
模型调用 → 工具执行 → 模型调用,这个循环不适合用 middleware/handler 链来描述。 - 为什么要区分编排层和能力层? 因为编排层(
run_agent.py)控制的是"什么时候做什么",能力层(tools/)控制的是"具体怎么做"。两者的变化频率不同:添加一个新工具不需要改动编排逻辑,改变对话策略不需要改动工具实现。
这个分层模型最大的价值是让你快速判断"某个问题属于哪一层"——定位到层之后,搜索范围立即缩小到几个文件。
入口层:用户如何触达 Agent
入口层回答一个问题:用户的消息从哪里进入系统?
Hermes 有五个入口,它们共享同一个编排层,但各自处理不同的交互模式:
| 入口 | 文件 | 行数 | 职责 |
|---|---|---|---|
| CLI 命令 | hermes_cli/main.py | — | hermes model、hermes tools、hermes config 等子命令入口 |
| 交互式 TUI | cli.py | 8736 | prompt_toolkit 驱动的终端交互界面,支持多行编辑、流式输出、slash command |
| 消息网关 | gateway/run.py | — | 同时连接 Telegram、Discord、Slack 等平台,接收消息并路由到 Agent |
| 批量运行 | batch_runner.py | — | 批量生成对话轨迹,用于 RL 训练和模型评估 |
| 定时调度 | cron/scheduler.py | — | croniter 驱动的定时任务,支持投递到消息平台 |
关键设计:这五个入口不是五个不同的 agent,而是同一个 AIAgent 的五种触发方式。它们通过不同的回调(callback)适配各自的 IO 模式,但核心编排逻辑完全共享。这就是为什么 cli.py 和 gateway/run.py 各有近万行代码——它们不是薄壳,而是各自领域的完整交互层。
详见第 13 章(CLI)、第 14 章(Gateway)、第 15 章(Cron)。
编排层:Agent 的大脑
编排层包含两个核心文件和一个支撑模块目录,它们构成整个系统的重心。
run_agent.py — AIAgent 核心编排器
run_agent.py 是全项目最大的单文件(9431 行),包含 AIAgent 类(定义在第 416 行)。它负责:
- 初始化决策链(config → memory → prompt → model client → session)
run_conversation()大循环:构建消息 → 调用模型 → 解析工具调用 → 执行工具 → 循环- Iteration budget 管理(防止 agent 无限循环)
- Fallback provider 切换(主模型不可用时自动降级)
- 11 个回调接口(
tool_progress_callback、stream_delta_callback、clarify_callback等,让同一个内核适配 CLI、Gateway、Batch 三种场景) - Token 使用量追踪与成本估算
详见第 4 章。
model_tools.py — 工具发现与调度
model_tools.py 是 agent 与工具系统之间的桥梁。它的核心职责:
_discover_tools():在启动时导入所有工具模块,触发自注册(model_tools.py:132)get_tool_definitions():根据 enabled/disabled toolset 过滤工具 schema(model_tools.py:234)handle_function_call():接收模型返回的工具调用,路由到正确的 handler(model_tools.py:459)_run_async():async→sync 桥接,让异步工具在同步编排器中运行(model_tools.py:81)
详见第 6 章。
agent/ — 编排支撑模块
agent/ 目录是编排层的"工具箱"——25 个模块提供 prompt 构建、记忆管理、模型适配、重试策略等横切关注点。这些模块本身不驱动对话循环,而是被 AIAgent 在循环的不同阶段调用。
agent/
├── prompt_builder.py # System prompt 多块拼装
├── prompt_caching.py # Prompt cache 优化
├── memory_manager.py # 记忆编排器
├── memory_provider.py # MemoryProvider ABC (17 个方法)
├── builtin_memory_provider.py # 内置记忆(MEMORY.md + USER.md)
├── context_compressor.py # 上下文压缩
├── context_engine.py # 可插拔上下文引擎 ABC
├── model_metadata.py # 模型能力探测与缓存
├── usage_pricing.py # Token 成本追踪
├── retry_utils.py # 重试与退避策略
├── skill_utils.py # 技能发现与解析
├── smart_model_routing.py # 智能模型路由
├── anthropic_adapter.py # Anthropic API 适配
├── credential_pool.py # API key 池管理
├── context_references.py # 上下文引用追踪
├── redact.py # 敏感信息脱敏
└── ... (共 25 个文件)
之所以把 agent/ 放在编排层而非状态层,是因为它的模块绝大多数服务于编排逻辑——prompt 构建、模型路由、重试策略都是"如何驱动对话"的一部分。其中 memory_manager.py、memory_provider.py、context_compressor.py 虽然处理状态,但它们的调用者是编排层的 AIAgent,从依赖方向看属于编排层的组成部分。
各模块的深入分析分散在第 4-12 章和第 18-19 章。
能力层:Agent 能做什么
能力层是 Hermes 最"宽"的一层,包含三个子系统:工具、技能、插件。
工具系统 — tools/
tools/
├── registry.py # ToolRegistry 单例 + ToolEntry 元数据
├── terminal_tool.py # 命令执行(6 种后端)
├── file_tools.py # 文件读写
├── browser_tool.py # 浏览器自动化
├── delegate_tool.py # 子代理委托
├── mcp_tool.py # MCP 外部能力接入
├── skills_tool.py # 技能检索与管理
├── code_execution_tool.py # Python/JS 代码执行
├── session_search_tool.py # 跨会话搜索
├── process_registry.py # 后台进程追踪
├── browser_providers/ # 浏览器后端(Browserbase, Firecrawl 等)
└── ... (共数十个 .py 文件,含子目录)
工具系统的核心抽象是 ToolRegistry(tools/registry.py:48)和 ToolEntry(tools/registry.py:24)。每个工具在模块加载时调用 registry.register() 自注册,但对仓库内置工具来说,仍然需要把模块接入 _discover_tools() 并纳入 toolsets.py,这样它才会真正暴露给模型。
数十个工具相关文件被分组为若干 toolset(如 terminal、web、memory),通过 toolsets.py 管理可用性。用户可以按 toolset 粒度启用/禁用工具。
详见第 6、7 章。
技能系统 — skills/
skills/
├── apple/ # macOS 自动化
├── autonomous-ai-agents/ # 自主代理模式
├── creative/ # 创意写作
├── data-science/ # 数据分析
├── devops/ # 运维自动化
├── diagramming/ # 图表生成
├── domain/ # 领域知识
└── ... (共 26 个类别)
技能是 Hermes "self-improving" 理念的核心。它们不是静态文档,而是 Markdown + YAML frontmatter 格式的过程性知识,agent 可以在工作中自主创建和改进。技能的发现、索引和条件加载由 agent/skill_utils.py 处理。
详见第 8 章。
插件系统 — plugins/
plugins/
└── memory/
├── honcho/ # Plastic Labs 用户建模
├── hindsight/ # 时序滑动窗口记忆
├── mem0/ # 向量数据库后端
├── holographic/ # 压缩全息存储
├── openviking/ # 语义嵌入记忆
├── retaindb/ # 保留策略记忆
├── supermemory/ # 多源聚合记忆
└── byterover/ # 替代向量存储
当前代码库里最成熟的插件生态确实集中在 memory provider,提供 8 个可选的外部记忆后端。每个 provider 实现 MemoryProvider ABC(agent/memory_provider.py,定义了 17 个方法);在运行时,外部 provider 由 MemoryManager 编排,而内置记忆仍主要走 MemoryStore 的直接接线。
详见第 11 章。
状态层:Agent 记住什么
状态层的核心是 hermes_state.py,它实现了基于 SQLite 的会话持久化。
| 组件 | 文件 | 职责 |
|---|---|---|
| SessionDB | hermes_state.py (1304 行) | SQLite 持久化:sessions 表 + messages 表 + messages_fts 虚拟表(FTS5 全文检索) |
| WAL 并发安全 | hermes_state.py:164-214 | BEGIN IMMEDIATE 事务 + 15 次 jitter retry,支持 Gateway 多平台并发写入 |
| FTS5 检索 | hermes_state.py | 跨会话全文搜索,支撑 session_search 工具和 LLM 摘要回忆 |
SessionDB 类(hermes_state.py:115)是一个精简的 SQLite 封装,不使用 ORM。这个选择是有意的:Hermes 需要在 $5 VPS 上运行,SQLite 的零配置和单文件部署比 PostgreSQL 更适合这个场景。WAL 模式让 Gateway 的多个平台可以并发读取,而写入通过 application-level jitter retry 解决锁竞争。
详见第 10 章。
平台层:Agent 如何到达用户
gateway/platforms/ — 17 个平台类型
gateway/platforms/
├── base.py # BasePlatformAdapter ABC
├── telegram.py # Telegram(webhook + polling)
├── telegram_network.py # Telegram 网络传输层(telegram.py 的辅助模块)
├── discord.py # Discord(多 guild、线程、反应)
├── slack.py # Slack(多 workspace、线程)
├── whatsapp.py # WhatsApp(Twilio 桥接)
├── signal.py # Signal(signal-cli 桥接)
├── matrix.py # Matrix(E2E、spaces)
├── email.py # Email(IMAP/SMTP)
├── feishu.py # 飞书
├── wecom.py # 企业微信
├── weixin.py # 个人微信
├── dingtalk.py # 钉钉
├── api_server.py # REST API 端点
├── homeassistant.py # Home Assistant IoT
├── mattermost.py # Mattermost
├── sms.py # SMS(Twilio)
└── webhook.py # 自定义 Webhook
所有平台适配器继承 BasePlatformAdapter ABC(gateway/platforms/base.py:470),实现 connect()、disconnect()、send() 等核心方法。Gateway 进程可以同时连接多个平台,每个平台的会话通过 SessionSource / SessionContext(gateway/session.py)独立管理。
详见第 14 章。
acp_adapter/ — Agent Client Protocol
acp_adapter/
├── entry.py # 入口模块
├── server.py # ACP HTTP 服务器
├── session.py # ACP 会话管理
├── tools.py # ACP 工具暴露
├── auth.py # 认证
├── permissions.py # 权限控制
└── events.py # SSE 事件流
ACP Adapter 让外部系统通过 Agent Client Protocol 标准接口与 Hermes 交互。目前已实现核心的 HTTP 服务、会话管理、SSE 事件流、认证和权限模块。
详见第 23 章。
源码走读:从入口到编排的调用链
光看目录结构还是抽象的。下面用一个最简单的例子——用户在 CLI 中发送一条消息——追踪代码如何从入口层流入编排层。
第一步:CLI 入口
用户运行 hermes 命令,hermes_cli/main.py 的 main() 函数被调用。对于交互式会话,它转发到 cli.py 的 run_cli() 函数:
# cli.py 中的核心调用(简化)
agent = AIAgent(
config=config,
session_db=session_db,
# ... 回调适配
stream_delta_callback=on_stream_delta,
clarify_callback=on_clarify,
)
response = agent.run_conversation(user_message)
关键观察:cli.py 构造 AIAgent 时注入了 TUI 专用的回调函数(如 stream_delta_callback 用于流式渲染、clarify_callback 用于交互式确认)。这些回调是入口层与编排层之间的适配接口。
第二步:Gateway 入口(对比)
Gateway 走的是另一条路径,但最终也构造同一个 AIAgent:
# gateway/run.py 中的核心调用(简化)
agent = AIAgent(
config=config,
session_db=session_db,
# ... 不同的回调适配
stream_delta_callback=on_platform_stream,
status_callback=on_platform_status,
)
response = agent.run_conversation(platform_message)
相同的 AIAgent,不同的回调——这就是"共享编排层 + 可插拔入口"的工程实现。第 3 章会完整追踪这条调用链从 run_conversation() 到模型调用再到工具执行的全过程。
第三步:编排层分发
进入 AIAgent.run_conversation() 后,编排层通过 model_tools.py 将工具调用分发到能力层:
# model_tools.py:459(简化)
def handle_function_call(name, arguments, ...):
entry = registry.get(name) # 从 ToolRegistry 查找
if entry.is_async:
return _run_async(entry.handler(**arguments))
return entry.handler(**arguments)
registry.get(name) 查找的是能力层的 ToolRegistry,entry.handler 指向具体工具的实现函数。编排层不知道工具的具体逻辑,只负责调用和收集结果。
第 3 章将完整展开这条调用链。
测试目录:架构的另一面镜子
tests/
├── run_agent/ → 编排层(AIAgent 大循环)
├── tools/ → 能力层(工具系统)
├── skills/ → 能力层(技能系统)
├── agent/ → 编排支撑(agent/ 模块)
├── gateway/ → 平台层(Gateway + 平台适配)
├── hermes_cli/ → 入口层(CLI 命令)
├── cli/ → 入口层(TUI 交互)
├── cron/ → 入口层(定时调度)
├── acp/ → 平台层(ACP 适配)
├── plugins/ → 能力层(插件系统)
├── honcho_plugin/ → 能力层(Honcho 记忆插件)
├── environments/ → 研究(RL 环境)
├── e2e/ → 端到端集成测试
├── integration/ → 集成测试
└── fakes/ → 测试用 mock 对象
测试目录的结构几乎是生产代码的镜像。如果你想了解某个子系统的行为,往往从对应的测试目录入手比直接读源码更高效——测试用例展示了"这个模块应该怎么被使用"。
400+ 个测试文件共享一个关键基础设施:tests/conftest.py 中的 _isolate_hermes_home fixture(标记为 autouse=True)。它在每个测试运行前将 HERMES_HOME 重定向到临时目录,确保测试之间完全隔离——不会读到用户的真实配置,也不会写入用户的 ~/.hermes。这个设计让 400+ 个测试可以安全地并行运行。
详见第 22 章。
支撑文件:不在五层之内但不可忽略
| 文件 | 职责 |
|---|---|
toolsets.py | 工具分组定义,控制 toolset 可用性 |
toolset_distributions.py | Toolset 分发配置 |
hermes_constants.py | 全局常量 |
hermes_logging.py | 日志配置 |
hermes_time.py | 时间工具函数 |
utils.py | 通用工具函数 |
mcp_serve.py | MCP 服务器模式(Hermes 作为 MCP server 暴露能力) |
mini_swe_runner.py | 小型 SWE benchmark 运行器 |
rl_cli.py | RL 训练 CLI |
trajectory_compressor.py | 轨迹压缩(训练数据处理) |
设计启示
Hermes 的五层分层服务于一个核心目标:让同一个 agent 内核能同时服务于五种不同的触发方式。这个设计带来三个直接收益:
- Bug fix 自动传播:编排层的修复(如 retry 逻辑改进)自动惠及 CLI、Gateway、Cron 所有入口
- 平台扩展解耦:新增消息平台只需实现
BasePlatformAdapter,不需要改动编排层或能力层 - 测试分层独立:每一层可以独立测试,不依赖其他层的真实实现
第 3 章将从编排层的 run_conversation() 入手,展开一次完整请求在系统中的旅程。
设计赌注回扣:本章呈现的五层架构直接服务于 Run Anywhere 赌注——正因为编排层与平台层彻底分离,Hermes 才能同时跑在终端、Telegram、Discord 和定时任务中。
版本演化说明
本章核心分析基于 Hermes Agent v0.8.0(2026 年 4 月)。 目录结构和文件数量可能随版本迭代变化,但五层架构模型和层间依赖方向在可预见的未来不会改变。