Hermes Agent v0.8.0 · 源码解析

Nous Research 开源的自进化 AI agent。本文基于本地 git clone 的 v0.8.0 源码 (commit d6785dc,2026-04-12),结合 AGENTS.mdRELEASE_v0.8.0.md 和 842 个 Python 文件的结构化阅读。所有技术结论都标注了 file:line 引用。

version 0.8.0 commit d6785dc python files 842 markdown files 542 repo size 162 MB test count ~3000 license MIT

01一句话总结

Hermes Agent 是一个多平台入口、同一个 agent 循环、可插拔 LLM provider、可插拔 terminal 后端、 可插拔 skills + MCP + plugins 的 Python agent 框架。它的最关键设计不是 LLM 能力,而是 把"跟 agent 说话"、"agent 跑代码"、"agent 用工具"、"agent 记忆"四件事完全正交: 你可以从 CLI / Telegram / Slack / Discord / WhatsApp / Signal / 邮件 / Matrix / 钉钉 / 飞书 / WeChat 任意入口发消息,同一个 AIAgent 实例处理,代码执行可以转发到本地 / Docker / SSH 远程 / Daytona / Modal / Singularity 任意沙箱,而模型 provider 从 OpenRouter / Nous / Anthropic / OpenAI / z.ai / Kimi / MiniMax / Google AI Studio / HuggingFace / 任意 OpenAI 兼容 custom endpoint 任选。

所有这些都可以在运行时用 /model 切换,不重启,不丢上下文。

02取证方式 · 不是 AI 猜的

本文的每一条技术断言都基于对实际源码的阅读,格式是 file:line 引证。 证据来源:

为什么要讲取证:AI 分析源码最容易出的问题是"听起来对但其实瞎编"。 本文写作时使用的读法是先读 → 再写 → 不反过来。若发现任何引用错误,以 GitHub 上 v0.8.0 标签为准,反馈到项目 issues。

03三个入口脚本

pyproject.toml pyproject.toml:112-115 定义了 3 个可执行入口:

命令指向作用
hermeshermes_cli.main:main交互式 CLI + 所有 hermes * 子命令
hermes-agentrun_agent:main单次 agent 调用(脚本模式、批处理、数据生成)
hermes-acpacp_adapter.entry:mainACP 协议服务器(给 VS Code / Zed / JetBrains 用)

核心类 AIAgent 定义在 run_agent.py:492。 交互 CLI 的 HermesCLI 定义在 cli.py:1574。 消息网关的 GatewayRunner 定义在 gateway/run.py:512这三个类是理解 Hermes 的三把钥匙

04整体架构

┌─────────────────────────────────────────────┐ 用户入口 CLI · Telegram · Slack · Discord WhatsApp · Signal · Email · Matrix DingTalk · Feishu · WeCom · WeChat · 22+ └──────────────────┬──────────────────────────┘[gateway/run.py + gateway/platforms/*] │ ▼ ┌─────────────────────────────────────────────────────────────────────┐ AIAgent 核心(run_agent.py:492) ┌──────────────┬──────────────┬──────────────┬──────────────┐ │ 系统提示词 │ 会话存储 │ 内存管理 │ 工具编排 │ │ agent/ │ hermes_state │ agent/ │ model_tools │ │ prompt_ │ .py (FTS5) │ memory_ │ + tools/ │ │ builder.py │ │ manager │ registry.py │ └──────────────┴──────────────┴──────────────┴──────────────┘ 主循环:run_conversation() :7528 while api_call_count < max_iterations: call LLM with tool schemas if response.tool_calls: handle_function_call(...) append tool results else: return response.content └──────────────────┬──────────────────┬───────────────────────────────┘ │ │ LLM 层 工具执行层 │ │ ▼ ▼ ┌─────────────────────┐ ┌──────────────────────────────────────────┐ OpenRouter tools/*.py(40+ 工具) Nous Portal ┌────────────────────────────────────┐ Anthropic │ 终端(terminal_tool.py) │ OpenAI + Codex │ ↓ 可选 6 种后端 │ Google AI Studio │ local · docker · ssh · │ z.ai / GLM │ modal · daytona · singularity │ Kimi / Moonshot └────────────────────────────────────┘ MiniMax · 文件(file_tools.py) HuggingFace · 网络(web_tools, browser_tool) xAI / Grok · 代码执行(code_execution_tool) custom(任意 OAI) · 子代理(delegate_tool) └─────────────────────┘ · MCP 客户端(mcp_tool.py,2195 行) └──────────────────────────────────────────┘

05仓库结构地图

来源:AGENTS.md 第 12-64 行 + 实际 ls 验证。

路径行数责任
run_agent.py10 578AIAgent 类 + 主循环 + 重试 / fallback / 预算管理
cli.py9 920HermesCLI 类 + 交互 TUI(prompt_toolkit + Rich)
gateway/run.py8 844GatewayRunner + 消息分发 + agent 会话缓存
tools/mcp_tool.py2 195MCP 客户端(OAuth 2.1 PKCE + 动态工具发现)
tools/terminal_tool.py1 777终端执行编排(后台进程、审批、环境切换)
hermes_state.py1 238SessionDB(SQLite + FTS5 全文搜索)
tools/delegate_tool.py1 103子代理派发(并行工作流)
model_tools.py577工具编排层 + sync→async 桥接
toolsets.py655工具集定义 + 平台启用策略
tools/registry.py335单例工具注册表 + dispatch
agent/prompt_builder, context_compressor, prompt_caching, memory_manager, trajectory, display
hermes_cli/所有 hermes * 子命令 + 配置 + 皮肤 + setup 向导
tools/40+ 工具文件(每个自注册到 registry)
tools/environments/3 2856 种终端后端(base + local/docker/ssh/modal/daytona/singularity)
gateway/platforms/22 个消息平台适配器
skills/26 个顶层分类 / 78 个 bundled skill
acp_adapter/ACP 协议服务(VS Code / Zed / JetBrains 集成)
cron/调度器(jobs.py + scheduler.py)
tinker-atropos/RL 训练子模块(Tinker + Atropos)
tests/~3000 个 pytest 测试

06核心:AIAgent 类

class AIAgent 定义在 run_agent.py:492。构造函数接受 50+ 参数, 但核心字段几个:

字段默认说明
model""(运行时注入)模型名,如 anthropic/claude-opus-4.6
max_iterations90 run_agent.py:527工具调用循环上限
iteration_budgetIterationBudget(90) run_agent.py:619跨主 agent + 所有子代理共享的预算
platformNone"cli" / "telegram" / "discord" / ...,用于注入平台格式提示
session_id自动生成SessionDB 外键
enabled_toolsets / disabled_toolsetsNone白/黑名单控制可用工具集
fallback_modelNone主 provider 失败后的备选
credential_poolNone多 key 自动轮替
skip_context_files / skip_memoryFalse批处理/RL 场景不注入用户人格

注意 run_agent.py:504-505_context_pressure_last_warned — 这是一个类级别的 dict,用来跨实例去重"上下文压力警告"。原因写在注释里: gateway 会为每条消息创建一个新 AIAgent 实例,所以实例级的 flag 每次都重置,需要类级共享状态。 这个细节暴露了 Gateway 的真实调用模式:每消息一个实例,但实例是从 cache 里拿的(见 gateway/run.py:577-584_agent_cache)。

07Agent 主循环

主循环在 run_conversation(),入口 run_agent.py:7528, 核心 while 在 run_agent.py:7850:

while (api_call_count < self.max_iterations and self.iteration_budget.remaining > 0) or self._budget_grace_call:
    self._checkpoint_mgr.new_turn()
    if self._interrupt_requested:
        interrupted = True
        break
    api_call_count += 1
    ...
    # 调 LLM
    response = client.chat.completions.create(model=model, messages=messages, tools=tool_schemas)
    if response.tool_calls:
        for tool_call in response.tool_calls:
            result = handle_function_call(tool_call.name, tool_call.args, task_id)
            messages.append(tool_result_message(result))
    else:
        return response.content

这个简化版是 AGENTS.md 第 112-122 行给的示意。真实代码在 7850-10213 之间展开了 ~2000 行复杂度,处理:

设计原则:AGENTS.md 第 339-347 行明确写 Prompt Caching Must Not Break。 主循环的一条铁律是——绝对不能中途改变过去的 context、工具集或系统提示。 破坏 cache 意味着 10x 成本。唯一允许改 context 的时刻是自动上下文压缩 (agent/context_compressor.py)。

08工具注册表(插件化核心)

Hermes 的工具系统是"tool 文件自注册"模型。每个 tools/*.py 在模块 import 时调用 registry.register(...) 把自己挂上去。看 tools/registry.py 的类定义:

tools/registry.py:48
class ToolRegistry:
    """Singleton registry that collects tool schemas + handlers from tool files."""

    def __init__(self):
        self._tools: Dict[str, ToolEntry] = {}
        self._toolset_checks: Dict[str, Callable] = {}

    def register(
        self,
        name: str,
        toolset: str,
        schema: dict,          # OpenAI function schema
        handler: Callable,     # 实际执行函数
        check_fn: Callable = None,   # 可用性检查(env 变量等)
        requires_env: list = None,
        is_async: bool = False,
        description: str = "",
        emoji: str = "",
        max_result_size_chars: int | float | None = None,
    ): ...

关键设计:

import 依赖链(防循环) tools/registry.py:7-14: tools/registry.py 不依赖任何其他文件 → tools/*.py 依赖 registry → model_tools.py 依赖 registry + 所有 tool 文件 → 上层 run_agent.py / cli.py / batch_runner.py 依赖 model_tools。

09工具编排层(sync/async 桥接)

model_tools.py 的顶部(model_tools.py:1-22)自述为 "thin orchestration layer over the tool registry"。它只做两件事:

  1. 触发 tool 发现:import 所有 tools/*.py,让它们 self-register
  2. sync→async 桥接:_run_async() 位于 model_tools.py:81-120+

_run_async() 背后有一个很讲究的设计 —— 三种 loop 策略:

场景策略为什么
CLI 主线程(无 running loop)持久共享 loop _tool_loop model_tools.py:39-56缓存的 httpx/AsyncOpenAI 客户端需要绑定到活 loop,asyncio.run() 每次 create-and-close 会触发 "Event loop is closed" GC 报错
Gateway 已经在 async 栈里开一次性 thread pool,在新线程里 asyncio.run() model_tools.py:108-113不跟现有 running loop 冲突
并行 tool 执行的 worker 线程每线程持久 loop(ThreadLocal)model_tools.py:59-78避免多 worker 抢同一个 loop,同时保持客户端缓存有效

这段代码读下来能看出 Hermes 是经过大量实战打磨的 —— _get_worker_loop() 的文档注释 直接说"这是为了避免某个 PR 引入的 'Event loop is closed' bug"。

10会话存储 + FTS5 搜索

hermes_state.pySessionDB 类是整个 agent 的记忆骨架。路径:

hermes_state.py:32  DEFAULT_DB_PATH = get_hermes_home() / "state.db"
hermes_state.py:34  SCHEMA_VERSION = 6
hermes_state.py:115 class SessionDB

表结构

sessionshermes_state.py:41-69 存每个会话的元数据:

messageshermes_state.py:71-85 存每条消息: session_id, role, content, tool_call_id, tool_calls, tool_name, timestamp, token_count, finish_reason, reasoning, reasoning_details, codex_reasoning_items

注意 reasoning_detailscodex_reasoning_items 是两种不同 provider 的 thinking 格式 —— Anthropic thinking blocks 和 OpenAI Codex reasoning item,各自持久化。

FTS5 全文搜索

hermes_state.py:93-112 定义了 FTS5 虚拟表 + 3 个触发器:

CREATE VIRTUAL TABLE IF NOT EXISTS messages_fts USING fts5(
    content,
    content=messages,
    content_rowid=id
);

CREATE TRIGGER messages_fts_insert AFTER INSERT ON messages BEGIN
    INSERT INTO messages_fts(rowid, content) VALUES (new.id, new.content);
END;
CREATE TRIGGER messages_fts_delete AFTER DELETE ON messages BEGIN
    INSERT INTO messages_fts(messages_fts, rowid, content) VALUES('delete', old.id, old.content);
END;
CREATE TRIGGER messages_fts_update AFTER UPDATE ON messages BEGIN
    INSERT INTO messages_fts(messages_fts, rowid, content) VALUES('delete', old.id, old.content);
    INSERT INTO messages_fts(rowid, content) VALUES (new.id, new.content);
END;

content=messages, content_rowid=id 是 FTS5 的外部内容模式(external content table)— 索引数据不重复存储,直接引用 messages 表的 id。节省一半磁盘。

_sanitize_fts5_query() hermes_state.py:938 实现了 FTS5 查询消毒 —— 原因:FTS5 自己有查询语法,用户输入的 -"() 要么 escape 要么会抛错。这个函数的注释明确说明"wrap unquoted hyphenated and dotted terms in quotes so FTS5's tokenizer doesn't split them"(因为 FTS5 会按点和连字符分词)。

高并发写

hermes_state.py:123-136 的类常量:

注释解释:SQLite 自带 busy handler 用确定性 sleep 导致"convoy effect"(高并发时互相卡成一队), 所以 Hermes 在应用层做抖动重试,让多个 writer 自然错开。这是细节工程能力的体现。

11消息网关

class GatewayRunner gateway/run.py:512。 启动入口 async def start_gateway() gateway/run.py:8634

关键实例字段

临时配置注入

gateway/run.py:540-549:

self._prefill_messages = self._load_prefill_messages()
self._ephemeral_system_prompt = self._load_ephemeral_system_prompt()
self._reasoning_config = self._load_reasoning_config()
self._service_tier = self._load_service_tier()
self._show_reasoning = self._load_show_reasoning()
self._busy_input_mode = self._load_busy_input_mode()
self._restart_drain_timeout = self._load_restart_drain_timeout()
self._provider_routing = self._load_provider_routing()
self._fallback_model = self._load_fallback_model()
self._smart_model_routing = self._load_smart_model_routing()

这些都标注为"ephemeral, injected at API-call time only and never persisted" — 典型做法:保留功能,但不污染持久化会话,这样切换配置不会破坏历史 cache。

1222 个平台适配器

gateway/platforms/ 目录实际文件清单(ls 核对):

api_server.py         base.py             bluebubbles.py    dingtalk.py
discord.py            email.py            feishu.py         helpers.py
homeassistant.py      matrix.py           mattermost.py     signal.py
slack.py              sms.py              telegram.py       telegram_network.py
webhook.py            wecom.py            wecom_callback.py wecom_crypto.py
weixin.py             whatsapp.py

base.py 定义 BasePlatformAdapter 抽象基类,其他文件继承实现。 ADDING_A_PLATFORM.md 在同目录下给开发者文档。

特殊的几个:

136 种 Terminal 后端

tools/environments/ 目录(共 3285 行代码):

后端文件 / 行数用途
basebase.py / 579抽象基类 + 共用工具方法
locallocal.py / 314直接在 Hermes 进程宿主机跑(默认,不安全)
dockerdocker.py / 560每个 task 起独立容器,用完销毁
sshssh.py / 258远程执行(把 Hermes 留本地,命令转发到 sandbox VM)
modalmodal.py / 434 + managed_modal.py / 282Modal.com 无服务器沙箱
daytonadaytona.py / 229Daytona workspace
singularitysingularity.py / 262HPC / 学术 Singularity 容器(给 GPU 集群用)
file_syncfile_sync.py / 168本地 ↔ 远程文件同步层(ssh/modal 共用)
为什么这是 Hermes 的亮点:传统 agent 把代码执行和 agent 本体绑死,要隔离就整个进程全隔离。 Hermes 把它解耦—— agent 跑在便宜的 VPS,代码执行可以转发到贵的 Modal GPU 或远程 SSH 沙箱, 按需付费。对比昨天讨论的 Manus 架构,Hermes 更灵活(你能选),Manus 更省心(强制 microVM)。

14Skills 系统

Skills 是 Hermes 的"过程性记忆"—— markdown 文件描述某类任务的步骤。skills/ 目录:

顶层分类包含
appleapple-reminders / imessage / findmy / apple-notes / ...
researchblogwatcher / polymarket / llm-wiki / arxiv / research-paper-writing
gamingminecraft-modpack-server / pokemon-player / ...
software-development(多个)
devops, mlops, data-science(多个)
creative, diagramming, email, feeds, gifs, github, leisure, media, mcp, note-taking, productivity, red-teaming, smart-home, social-media, autonomous-ai-agents, domain, dogfood, index-cache, inference-sh

26 个顶层 + 78 个 bundled skill 文件(入口日志 "78 total bundled" 匹配)。 每个 skill 是 SKILL.md,YAML frontmatter 带 metadata。例:

---
name: dogfood
description: Systematic exploratory QA testing of web applications ...
version: 1.0.0
metadata:
  hermes:
    tags: [qa, testing, browser, web, dogfood]
    related_skills: []
---

# Dogfood: Systematic Web Application QA Testing
...

来源:skills/dogfood/SKILL.md:1-11

Skills 的注入策略:agent/skill_commands.py 扫描 ~/.hermes/skills/, 用户打 /skillname 触发时,注入为用户消息(不是系统提示词), 这样不破坏 prompt caching —— 见 AGENTS.md:135(原文: "Skill slash commands: scans skills dir, injects as user message (not system prompt) to preserve prompt caching")。

此外,skills 还支持"自我进化"—— 独立仓库 hermes-agent-self-evolution 用 DSPy + GEPA 对 skill 做基于轨迹的 prompt 优化。

15Slash 命令中心化注册

所有 /command 定义在 hermes_cli/commands.pyCOMMAND_REGISTRY(AGENTS.md:137-148)。 一次定义,多处消费:

加一条 slash 命令只需要在 CommandDef 数组里加一行 + 各自 process_command() 里加 elif canonical == "mycommand": 分支。 加别名更简单 —— 只动 aliases 元组,所有下游自动同步 (dispatch、help、Telegram 菜单、Slack 映射、autocomplete)。

16Profile 多实例支持

Hermes 支持多个完全隔离的实例,每个有独立 HERMES_HOME。核心机制:

hermes_cli/main.py 中的 _apply_profile_override()
    在任何模块 import 之前设置 HERMES_HOME 环境变量
    → 所有 119+ 个 get_hermes_home() 调用自动 scope 到活 profile

安全规则(AGENTS.md:376-420):

真实修过 5 个 bug:AGENTS.md 第 427 行提到"This was the source of 5 bugs fixed in PR #3575" —— Nous Research 自己踩过坑。硬编码 ~/.hermes 会破坏 profile 隔离, 测试里也要 mock Path.home() + 设 HERMES_HOME 才能跑 profile 测试 (tests/conftest.py_isolate_hermes_home autouse fixture)。

17v0.8.0 重要变化

来源:RELEASE_v0.8.0.md(2026-04-08 发布,209 PR / 82 issue 合并)。挑出最关键的:

A. 自我进化的证据

#6120 — "Self-Optimized GPT/Codex Tool-Use Guidance"。原文: "The agent diagnosed and patched 5 failure modes in GPT and Codex tool calling through automated behavioral benchmarking"。这句话很重:agent 用自己的行为基准测试发现自己的问题, 然后自己写了修复。不是团队加提示词,是 agent 闭环在做迭代。

B. 后台任务自动通知

#5779notify_on_complete。起一个长任务(训练、测试、部署), agent 不需要 polling,完成时自动收通知。这是长时间任务场景的核心优化。

C. 闲置超时替代墙钟超时

#5389 — 原文:"Gateway and cron timeouts now track actual tool activity instead of wall-clock time"。这个改动让"跑 20 分钟的 build 不会被超时杀掉", 前提是它一直在有工具活动。

D. 实时模型切换

#5181 / #5742/model 从 CLI、Telegram、Discord、Slack 都能切。 Telegram 和 Discord 还给了 inline 按钮 picker。

E. 安全加固打包

#5944 + #5613 + #5629 — "Security Hardening Pass":

F. MCP OAuth 2.1 PKCE

#5420 — 完整的 OAuth 2.1 (带 PKCE) 接入任意 MCP server。 #5305 — 自动 OSV 漏洞库扫描 MCP 扩展包(防供应链投毒)。

G. Google AI Studio 原生 provider

#5577 — Gemini 直连,不再必须过 OpenRouter。还集成了 models.dev 注册表 自动检测任意 provider 的 context length。

H. 集中式日志 + 结构验证

#5430~/.hermes/logs/agent.log + errors.log, hermes logs 命令跟踪过滤。#5426 — 启动时验证 config.yaml 结构。

18Docker 运行时

Dockerfile

来源:Dockerfile:1-46(46 行,简短)。三阶段构建:

  1. ghcr.io/astral-sh/uv:0.11.6-python3.13-trixie 作为 uv 源 Dockerfile:1
  2. tianon/gosu:1.19-trixie 作为 gosu 源 Dockerfile:2 — gosu 是用来丢 root 权限的轻量替代 su
  3. debian:13.4 作为最终 base Dockerfile:3

关键环境变量 Dockerfile:7-10:

APT 包列表 Dockerfile:14-15: build-essential nodejs npm python3 ripgrep ffmpeg gcc python3-dev libffi-dev procps —— 注意:这里没有 git。这是我们昨天部署时踩到的坑, npm install 需要 git 但 Dockerfile 没装,导致 build 失败,需要 sed 补丁。

entrypoint.sh

docker/entrypoint.sh:1-64:

19设计洞察(主观)

读完之后几个主观结论 —— 不是源码里写的,是我看源码之后的归纳:

1. 模块化的纪律比任何单点技术重要

Hermes 能支持 22 个消息平台 + 6 种终端后端 + 11 个 LLM provider + 78 个 skill,不是因为每个都写得多好, 而是因为工具注册表 + 抽象基类 + slash 命令中心化让新增成本低到可接受。 看 BasePlatformAdapterBaseEnvironmentToolRegistryCOMMAND_REGISTRY 这 4 个抽象,就明白为什么 v0.8 能在一个周期合并 209 个 PR。

2. 真正的性能优化都在"不重建"上

Gateway 的 _agent_cache_tool_loop 的持久 loop、Anthropic 的 prompt cache 保护…… 所有优化都是"能不重建就不重建"。这让 Hermes 对 prompt caching 友好的 LLM(Anthropic)比对不友好的 便宜 10 倍。这不是理论 —— AGENTS.md:339-347 把这个原则写成铁律

3. 它把"agent 能跑哪"和"agent 用啥模型"解耦了

传统 agent 把两件事绑一起。Hermes 的终端后端和 LLM provider 是两个独立的抽象: 你可以把 agent 放在 $5 VPS 上,代码执行转发到 Modal 上的 GPU 实例,模型调 Anthropic Opus, 三者都可以独立换。这个解耦是真正的产品差异化。

4. 它承认自己是"一个会用工具的 LLM",而不是"有 LLM 的应用"

主循环本质就是 while: LLM call → handle tool calls → repeat。 Hermes 的所有复杂度都在工具生态、平台适配、状态持久化、安全边界上 —— agent loop 本身 2000 行里大部分是错误处理、重试、审批、中断、预算、流式回调,而不是"AI 逻辑"。 这符合"agent 是一个模式,不是一种算法"的设计哲学。

5. 自进化不是魔法,是 DSPy + 基准测试闭环

v0.8 的 "Self-Optimized GPT/Codex Tool-Use Guidance"(#6120)揭示了自进化的实现路径: 跑 benchmark → 识别失败模式 → 用 DSPy/GEPA 优化 prompt → 回灌。 独立仓库 hermes-agent-self-evolution 印证这点。 换句话说,自进化是工程,不是玄学