Hermes Agent 核心架构分析(官网 Directory Structure + 架构图 + 源码心得)
Hermes Agent 核心架构分析
官网文档:https://hermes-agent.nousresearch.com/docs/developer-guide/architecture
源码路径:~/.hermes/hermes-agent/
分析版本:v0.13.0 (May 2026) + 官网架构文档 v0.13.0
一、整体架构
┌─────────────────────────────────────────────────────────────────────────┐
│ 入口层(Entry Points) │
│ │
│ CLI (cli.py) Gateway (gateway/run.py) ACP (acp_adapter/) │
│ Batch Runner API Server Python Library │
└──────────┬─────────────────┬──────────────────────┬────────────────────┘
│ │ │
▼ ▼ ▼
┌─────────────────────────────────────────────────────────────────────────┐
│ AIAgent (run_agent.py) │
│ 核心对话循环 ~15,700 LOC │
│ │
│ ┌─────────────────┐ ┌─────────────────┐ ┌─────────────────────────┐ │
│ │ Prompt Builder │ │ Provider 解析 │ │ Tool Dispatch │ │
│ │ (agent/prompt_ │ │ (hermes_cli/ │ │ (model_tools.py) │ │
│ │ builder.py) │ │ runtime_ │ │ │ │
│ │ │ │ provider.py) │ │ │ │
│ └────────┬────────┘ └────────┬────────┘ └────────────┬────────────┘ │
│ │ │ │ │
│ ┌────────▼────────┐ ┌────────▼────────┐ ┌───────────▼────────────┐ │
│ │ 压缩 & 缓存 │ │ 3 种 API 模式 │ │ Tool Registry (registry)│ │
│ │ ContextEngine │ │ chat_complet. │ │ 70+ 工具 │ │
│ │ (ABC 可插拔) │ │ codex_resp. │ │ 28 toolsets │ │
│ │ │ │ anthropic_ │ │ │ │
│ └────────────────┘ │ messages │ └─────────────────────────┘ │
│ └─────────────────┘ │
└─────────────────────────────────────────────────────────────────────────┘
│ │
▼ ▼
┌──────────────────────┐ ┌──────────────────────────────────────┐
| Session Storage │ │ Tool Backends │
| (SQLite + FTS5) │ │ Terminal (7 backends) │
| hermes_state.py │ │ Browser (5 backends) │
| gateway/session.py │ │ Web (4 backends) │
└──────────────────────┘ │ MCP (动态发现) │
│ File, Vision, etc. │
└──────────────────────────────────────┘
官方原文架构图说明:
AIAgent 是核心编排引擎,负责:系统提示词组装、Provider/API 模式选择、可中断的模型调用、工具执行、对话历史维护、压缩/重试/降级、迭代预算跟踪、上下文丢失前的记忆持久化。
二、Directory Structure(源码文件树)
来源:官网 Directory Structure
说明:仅列出有注释说明的文件,__init__.py及无描述的空文件已省略。
hermes-agent/ # Python 包根目录
│
├── run_agent.py # AIAgent 核心对话循环(大文件)
├── cli.py # HermesCLI 交互式终端(大文件)
├── model_tools.py # 工具编排层
├── toolsets.py # 工具分组与别名
├── hermes_state.py # SQLite + FTS5 会话存储 ★
├── hermes_constants.py # HERMES_HOME、profile 路径常量
├── hermes_bootstrap.py # Windows UTF-8 stdio 修复(必须在最早期 import)
├── hermes_logging.py # 日志基础设施
├── hermes_time.py # 时间工具
├── utils.py # 通用工具函数
│
├── agent/ # Agent 内部模块 ★
│ ├── prompt_builder.py # 系统提示词组装(8 层缓存)
│ ├── context_engine.py # ContextEngine ABC(可插拔)
│ ├── context_compressor.py # 默认有损摘要压缩引擎
│ ├── context_references.py # 上下文引用
│ ├── prompt_caching.py # Anthropic prompt caching
│ ├── anthropic_adapter.py # Anthropic Messages API 格式转换
│ ├── bedrock_adapter.py # AWS Bedrock 适配器
│ ├── codex_responses_adapter.py # OpenAI Codex/Responses API 适配器
│ ├── auxiliary_client.py # 辅助 LLM(视觉、摘要等)
│ ├── memory_manager.py # 记忆管理编排
│ ├── memory_provider.py # Memory Provider ABC
│ ├── display.py # KawaiiSpinner、工具预览格式化
│ ├── error_classifier.py # 错误分类
│ ├── file_safety.py # 文件安全检查
│ ├── model_metadata.py # 模型上下文长度、token 估算
│ ├── models_dev.py # models.dev 集成
│ ├── skill_commands.py # Skill slash 命令扫描和注入
│ ├── skill_preprocessing.py # Skill 预处理
│ ├── skill_utils.py # Skill 工具函数
│ ├── title_generator.py # 会话标题生成
│ ├── trajectory.py # 轨迹保存助手
│ ├── tool_guardrails.py # 工具护栏
│ ├── usage_pricing.py # 用量计费
│ ├── account_usage.py # 账户用量跟踪
│ ├── rate_limit_tracker.py # 速率限制跟踪
│ ├── redact.py # 内容脱敏
│ ├── retry_utils.py # 重试工具
│ ├── think_scrubber.py # 去除 think 标签
│ ├── image_gen_provider.py # 图像生成 Provider
│ ├── image_gen_registry.py # 图像生成注册表
│ ├── image_routing.py # 图像路由
│ ├── i18n.py # 国际化
│ ├── curator.py # Curator 相关
│ ├── copilot_acp_client.py # GitHub Copilot ACP 客户端
│ ├── credential_pool.py # 凭据池
│ ├── credential_sources.py # 凭据来源
│ └── transports/ # 传输层(3 种 API 模式的 HTTP 客户端)
│ ├── base.py # BaseTransport ABC
│ ├── chat_completions.py # OpenAI chat.completions 传输
│ ├── anthropic.py # Anthropic 传输
│ ├── codex.py # Codex 传输
│ ├── bedrock.py # Bedrock 传输
│ └── types.py # 传输层类型定义
│
├── hermes_cli/ # CLI 子命令和设置
│ ├── main.py # 所有 `hermes` 子命令入口(大文件)
│ ├── commands.py # 单一 COMMAND_REGISTRY(所有 slash 命令)
│ ├── config.py # DEFAULT_CONFIG、OPTIONAL_ENV_VARS、迁移
│ ├── auth.py # PROVIDER_REGISTRY、凭据解析
│ ├── models.py # 模型目录、provider 模型列表
│ ├── model_switch.py # /model 命令逻辑(CLI + gateway 共享)
│ ├── setup.py # 交互式设置向导(大文件)
│ ├── skin_engine.py # CLI 皮肤引擎
│ ├── skills_config.py # hermes skills — 每个平台启用/禁用
│ ├── skills_hub.py # /skills slash 命令
│ ├── tools_config.py # hermes tools — 每个平台启用/禁用
│ ├── plugins.py # PluginManager — 发现、加载、钩子
│ ├── callbacks.py # 终端回调(clarify、sudo、approval)
│ ├── gateway.py # hermes gateway 启动/停止
│ ├── cron.py # /cron 子命令
│ ├── kanban.py # Kanban 看板命令
│ ├── banner.py # Banner 显示
│ ├── checkpoints.py # 检查点
│ ├── claw.py # Claw 命令
│ ├── cli_output.py # CLI 输出
│ ├── clipboard.py # 剪贴板
│ ├── colors.py # 颜色工具
│ ├── completion.py # 自动补全
│ ├── curses_ui.py # Curses UI
│ ├── debug.py # 调试命令
│ ├── doctor.py # 健康检查
│ ├── logs.py # 日志命令
│ ├── mcp_config.py # MCP 配置
│ ├── memory_setup.py # 记忆设置
│ ├── platforms.py # 平台支持
│ ├── voice.py # 语音命令
│ └── webhook.py # Webhook 命令
│
├── tools/ # 工具实现(每个工具一个文件)
│ ├── registry.py # 中心工具注册表
│ ├── approval.py # 危险命令检测
│ ├── terminal_tool.py # 终端编排
│ ├── process_registry.py # 后台进程管理
│ ├── file_tools.py # read_file, write_file, patch, search_files
│ ├── web_tools.py # web_search, web_extract
│ ├── browser_tool.py # 10 个浏览器自动化工具
│ ├── code_execution_tool.py # execute_code 沙箱
│ ├── delegate_tool.py # 子 agent 委托
│ ├── mcp_tool.py # MCP 客户端(大文件)
│ └── environments/ # 终端后端
│ ├── local.py # 本地 shell 执行
│ └── docker.py # Docker 容器执行
│
├── gateway/ # 消息平台网关
│ ├── run.py # GatewayRunner — 消息分发(大文件)
│ ├── session.py # SessionStore — 对话持久化
│ ├── delivery.py # 出站消息投递
│ ├── pairing.py # DM 配对授权
│ ├── hooks.py # 钩子发现和生命周期事件
│ ├── mirror.py # 跨会话消息镜像
│ └── status.py # Token 锁、profile 范围进程跟踪
│
├── acp_adapter/ # ACP 服务端(VS Code / Zed / JetBrains)
├── cron/ # 调度器(jobs.py, scheduler.py)
├── plugins/memory/ # 记忆提供方插件
├── plugins/context_engine/ # 上下文引擎插件
├── skills/ # 内置技能(始终可用)
├── optional-skills/ # 官方可选技能(需显式安装)
├── website/ # Docusaurus 文档站
└── tests/ # Pytest 测试套件(~3,000+ 测试)
三、核心循环(run_agent.py,~15,700 LOC)
3.1 两个入口接口
# 简单接口 — 返回最终响应字符串
response = agent.chat("Fix the bug in main.py")
# 完整接口 — 返回 dict 含 final_response + messages + metadata + usage
result = agent.run_conversation(
user_message="Fix the bug in main.py",
system_message=None, # 自动构建,可选
conversation_history=None, # 自动从 session 加载,可选
task_id="task_abc123"
)
3.2 三种 API 执行模式
| API 模式 | 用途 | 客户端 |
|---|---|---|
chat_completions |
OpenAI 兼容端点(OpenRouter/自定义/大多数 Provider) | openai.OpenAI |
codex_responses |
OpenAI Codex / Responses API | openai.OpenAI(Responses 格式) |
anthropic_messages |
原生 Anthropic Messages API | anthropic.Anthropic(经 adapter) |
模式解析优先级:
- 构造函数显式
api_mode参数(最高) - Provider 特定检测(如
anthropicprovider →anthropic_messages) - Base URL 启发式(如
api.anthropic.com→anthropic_messages) - 默认:
chat_completions
三种模式在 API 调用前后都收敛到同一个内部消息格式(OpenAI 风格的 role/content/tool_calls 字典)。
3.3 循环生命周期(Turn Lifecycle)
每轮迭代遵循以下序列:
run_conversation()
1. 生成 task_id(若未提供)
2. 将用户消息追加到对话历史
3. 构建或复用缓存的系统提示词(prompt_builder.py)
4. 检查是否需要预压缩(上下文 >50%)
5. 从对话历史构建 API 消息
chat_completions → OpenAI 格式直接使用
codex_responses → 转换为 Responses API input items
anthropic_messages → 经 anthropic_adapter.py 转换
6. 注入临时提示词层(预算警告、上下文压力)
7. 若在 Anthropic 上应用 prompt caching 标记
8. 执行可中断 API 调用(_interruptible_api_call)
9. 解析响应:
若有 tool_calls → 执行,追加结果,循环回步骤 5
若为文本响应 → 持久化 session,必要时刷新记忆,返回
3.4 消息格式
所有消息在内部使用 OpenAI 兼容格式:
{"role": "system", "content": "..."}
{"role": "user", "content": "..."}
{"role": "assistant", "content": "...", "tool_calls": [...]}
{"role": "tool", "tool_call_id": "...", "content": "..."}
reasoning 内容存在 assistant_msg["reasoning"] 中。
四、工具系统(Tool System)
4.1 工具注册中心(tools/registry.py,~563 LOC)
核心:ToolEntry 元数据结构(__slots__ 优化内存):
class ToolEntry:
__slots__ = (
"name", # 唯一工具名(API schema 中使用)
"toolset", # 所属工具集
"schema", # OpenAI function-calling schema
"handler", # 工具被调用时执行的函数
"check_fn", # 可选:返回工具是否可用的布尔值
"requires_env", # 可选:所需环境变量(用于 UI 显示)
"is_async", # handler 是否为 async 协程
"description", # 人类可读描述
"emoji", # spinner/进度显示的 emoji
"max_result_size_chars", # 结果最大字符数
"dynamic_schema_overrides", # 运行时动态 schema 覆盖
)
4.2 自注册机制
每个工具文件在模块顶层调用 registry.register(...):
registry.register(
name="terminal",
toolset="terminal",
schema={...},
handler=handle_terminal,
check_fn=check_terminal,
requires_env=["TERM"],
is_async=False,
description="Run commands",
emoji="",
)
4.3 AST 自动发现(discover_builtin_tools)
model_tools.py 导入时调用 discover_builtin_tools(),通过 AST 解析扫描所有 tools/*.py 文件,找出含顶层 registry.register() 调用的模块并 import:
def _module_registers_tools(module_path: Path) -> bool:
tree = ast.parse(source)
# 只检查模块顶层语句,函数内的 helper 调用不误判
return any(is_registry_register_call(stmt) for stmt in tree.body)
4.4 check_fn TTL 缓存(30s)
check_fn 检查外部状态(Docker daemon / Modal SDK / playwright 等),缓存 30s 避免频繁探测,同时确保环境变量切换后 1-2 轮内生效。
4.5 动态 Schema Override
dynamic_schema_overrides 是零参 callable,在每次 get_definitions() 时调用,用于运行时动态修改 schema——例如 delegate_task 的描述反映用户当前的 max_concurrent_children 配置。
4.6 终端执行环境(tools/environments/)
| 环境 | 说明 |
|---|---|
local.py |
本地 shell 执行 |
docker.py |
Docker 容器执行 |
ssh.py |
远程 SSH 执行 |
modal.py |
Modal 云端执行 |
daytona.py |
Daytona 云端沙箱 |
managed_modal.py |
Modal 托管环境 |
singularity.py |
Singularity 容器 |
vercel_sandbox.py |
Vercel 沙箱 |
五、提示词系统(Prompt System)
5.1 核心设计原则
Hermes 刻意分离两类状态:
- 缓存的系统提示词(Cached system prompt)— 会话期间不变
- 临时的 API 调用时添加(Ephemeral additions)— 每轮动态变化
5.2 缓存系统提示词层次(组装顺序)
Layer 1: Agent Identity
└── SOUL.md(~/.hermes/SOUL.md),若无可用则用 DEFAULT_AGENT_IDENTITY
Layer 2: Tool-aware behavior guidance
└── 工具使用指导(GPT/Codex 模型专用)
Layer 3: Honcho static block(若激活)
Layer 4: Optional system message(来自配置或 API)
Layer 5: Frozen MEMORY snapshot
└── 持久记忆的快照(~/.hermes/memories/MEMORY.md)
Layer 6: Frozen USER profile snapshot
└── 用户配置文件快照(~/.hermes/memories/USER.md)
Layer 7: Skills index
Layer 8: Context files
└── AGENTS.md、.cursorrules、.cursor/rules/*.mdc
Layer 9: Timestamp / session ID / platform hint
5.3 Context Compression(双重压缩系统)
┌─────────────────────────┐
消息流入 │ Gateway Session Hygiene │ → 85% 上下文阈值(预压缩,粗估计)
└────────────┬────────────┘
▼
┌─────────────────────────┐
│ Agent ContextCompressor │ → 50% 上下文阈值(环内,真实 token)
└─────────────────────────┘
六、会话存储(hermes_state.py,~2,966 LOC)
6.1 数据库架构
~/.hermes/state.db (SQLite, WAL 模式)
├── sessions — Session 元数据、token 计数、计费
├── messages — 每个 session 的完整消息历史
├── messages_fts — FTS5 虚拟表(content + tool_name + tool_calls)
├── messages_fts_trigram — FTS5 trigram 分词器(CJK/子串搜索)
├── state_meta — 键/值元数据表
└── schema_version — 单行表,跟踪迁移状态
6.2 WAL 降级策略
WAL 模式在网络文件系统(NFS / SMB / CIFS / WSL1)上不兼容,遇到 SQLITE_PROTOCOL 错误时自动降级到 journal_mode=DELETE,牺牲并发换可靠性。
七、消息网关(Gateway Internals)
7.1 支持平台(20 个 Adapter)
telegram / discord / slack / whatsapp / signal / matrix / mattermost / email / sms / dingtalk / feishu / wecom / wecom_callback / weixin / bluebubbles / qqbot / homeassistant / webhook / api_server / yuanbao
7.2 Session Key 格式
agent:main:{platform}:{chat_type}:{chat_id}
八、设计原则(Design Principles)
| 原则 | 实践含义 |
|---|---|
| Prompt stability | 系统提示词在会话期间不改变。除非用户显式操作(如 /model),否则没有缓存破坏性变更。 |
| Observable execution | 每个工具调用都通过回调对用户可见。CLI 有 spinner 进度更新,gateway 有聊天消息进度。 |
| Interruptible | API 调用和工具执行可被用户输入或信号中途取消。 |
| Platform-agnostic core | 一个 AIAgent 类服务 CLI、gateway、ACP、batch 和 API server。平台差异在入口点,而非 agent 本身。 |
| Loose coupling | 可选子系统(MCP、插件、memory providers、RL 环境)使用注册表模式和 check_fn 门控,无硬依赖。 |
| Profile isolation | 每个 profile(hermes -p <name>)有自己的 HERMES_HOME、config、memory、sessions 和 gateway PID,多个 profile 可并发运行。 |
九、源码阅读心得
9.1 零循环依赖的自注册架构
tools/registry.py 处于依赖链的最底层——它不从任何其他模块导入。而每个工具文件(tools/*.py)只在模块顶层调用一次 registry.register(),然后由 model_tools.py 通过 AST 扫描发现并 import。
这条单向依赖链保证了:
- 新增工具只需在
tools/下创建新文件,无需修改任何注册表列表 registry.py的测试不需要 mock 任何其他模块- 工具模块之间无隐式耦合
9.2 Lazy Import 优化启动时间
run_agent.py 顶部用 _OpenAIProxy 代理类延迟了 openai.OpenAI 的导入,注释解释这能节省约 240ms 的 import 时间。这在 fire 刚安装完首次运行时特别有意义——避免 import run_agent 卡顿。
class _OpenAIProxy:
def __call__(self, *args, **kwargs):
return _load_openai_cls()(*args, **kwargs)
9.3 __slots__ 广泛使用
ToolEntry 使用 __slots__ 而非 __dict__ 存储属性,大幅减少每个工具元对象的内存占用(70+ 工具实例累积下来很可观)。代码库中其他频繁实例化的类也采用了类似做法。
9.4 Profile 隔离
每个 profile(hermes -p <name>)有独立的 HERMES_HOME 目录树,包括 config、memory、sessions、gateway PID。这通过 hermes_constants.py 中的 get_hermes_home() 实现,profile 名作为 subdirectory 嵌入路径。设计意图类似 Linux 多用户隔离,但靠目录实现而非命名空间。
9.5 Profile 开关对环境的影响
Profile 隔离下,不同 profile 的 gateway 可以并发运行,各自服务不同的消息平台。这对需要同时接入 Telegram(个人用)和 Discord(工作群)的场景很有价值。但这也意味着 token 计数、计费、memory 等都是 profile 独立的,不能跨 profile 共享。
9.6 TUI 的前后端分离
TypeScript Ink React 掌控渲染,Python 掌控逻辑,通过 newline-delimited JSON-RPC over stdio 通信。好处是前端可以用 React 生态,后端保持 Python 的工具链。但 stdin/stdout 作为传输协议意味着 TUI 不能通过 SSH 远程运行(需要 PTY 绑定本地终端)。
9.7 传输层的三套适配器
agent/transports/ 目录下对三种 API 模式分别有独立的传输实现。这比把所有逻辑塞进 run_agent.py 更清晰,但也意味着如果新增一个 Provider 需要修改多个传输文件。
9.8 平台适配器的统一抽象
BasePlatform ABC 定义了所有 20 个平台适配器的共同接口。但实际看 platforms/ 目录,很多平台有自己独特的字段和逻辑(如微信的加密、企微的回调)。这说明"统一抽象"是有代价的——adapter 层本身复杂度不低。
9.9 测试覆盖策略
代码库有 ~3,000+ 测试,大量使用 monkeypatch 和 patch()。test_tools/ 下的测试尤其多——每个工具一个或多个测试文件。这种广覆盖策略保证了工具系统的稳定性,但也意味着重构工具时需要同步更新大量测试。
十、关键文件速查
| 文件 | LOC | 职责 |
|---|---|---|
run_agent.py |
~15,700 | AIAgent 核心对话循环、三种 API 模式 |
cli.py |
~13,540 | HermesCLI 交互式终端 |
model_tools.py |
~865 | 工具编排层、asyncio 循环复用 |
tools/registry.py |
~563 | 工具注册中心(AST 发现) |
hermes_state.py |
~2,966 | SQLite + FTS5 会话存储 |
agent/prompt_builder.py |
— | 系统提示词组装(8 层缓存) |
agent/context_compressor.py |
— | 默认有损摘要压缩引擎 |
agent/transports/ |
— | 3 种 API 模式的 HTTP 客户端 |
hermes_cli/commands.py |
— | 单一 COMMAND_REGISTRY(所有 slash 命令) |
gateway/run.py |
— | GatewayRunner — 消息网关主循环 |
gateway/platforms/ |
— | ~20 个平台适配器 |
tools/environments/ |
— | 8 种终端执行环境 |

浙公网安备 33010602011771号