Hermes为什么好用?一文带你读懂最近爆火的Hermes(爱马仕)
1. Hermes 到底是什么?
Hermes 是一个与模型无关、能够自我进化的对话型智能体,它可以作为 CLI/TUI 在本地运行,也可以作为消息网关(Telegram/Discord/Slack/WhatsApp/Signal)跑在服务器上,或者作为定时 cron worker 运行。它最大的差异化在于闭环学习:在用工具解决问题的同时,它会写下可复用的 “skill” 文档,并维护一个持久化的记忆文件——这意味着这个 agent 真的会越用越聪明。从模型、工具、技能、记忆后端、执行环境到 UI,所有部分都是可插拔的。
(1)三个范式的演进
要看懂 Hermes 这种东西为什么存在,得先看 AI 工程范式过去三年走过的路。

| 范式 | 核心问题 | 优化对象 | 交互模式 |
|---|---|---|---|
| 提示词工程 | 怎么把话说清楚 | Prompt 的措辞、格式、示例 | 一问一答 |
| 上下文工程 | 怎么给 AI 喂信息 | 文档、代码片段、历史对话 | 信息注入 → 生成 |
| 驾驭工程 | 怎么让 Agent 可靠工作 | 约束、反馈回路、控制系统 | 人类掌舵,Agent 执行 |
一个好记的类比:
- Prompt Engineering —— 对马喊话的技巧
- Context Engineering —— 给马看的地图
- Harness Engineering —— 给马造一条高速公路,配上护栏、限速牌和加油站,让马儿按规矩跑
这不是术语升级,是工程现实碾出来的。Demo 跑通和生产可靠之间差着好几个数量级——prompt 和 context 都填不平这个鸿沟,你需要约束、反馈回路、护栏、可观测性、回退机制。Hermes 就是一个 harness:它的 47 个工具、四层审批、迭代预算、SessionDB——所有这些都不是「让模型变聪明」,是「让一个会犯错的幽灵在生产里能用」。imo 这就是 agent harness 这一代基础设施的全部价值。
(2)两条原则
在动手构建之前,先把这两条原则刻进脑子里:
- 一个 agent,多个 surface。 一个核心类驱动所有界面。各种 surface(CLI、gateway、cron、batch、API)都只是薄薄的入口层,负责构造一个 agent 并调用
AIAgent.run_conversation()。 - 程序化记忆 > 提示词花活。 大多数 “聪明 agent” 的行为不是来自提示词工程,而是来自 agent 拥有一个由它自己读写、可以持续生长的 markdown 文件夹(技能 + 记忆 + 人格)。
2. 核心原则
这些是 Hermes 遵循的设计规则。自己构建时也要记住——代码库里那些"奇怪"的决策,大多都能追溯到其中某一条。
(1)平台无关的核心
agent 自身并不知道它运行在终端、Telegram 聊天还是 cron 任务里。所有平台相关的逻辑都活在适配器里,适配器把平台事件翻译成 agent.run_conversation(...),再把响应翻译回去。如果你发现自己正在 agent 核心代码里加 if 分支处理某个特定平台,那你已经偏离架构了。
(2)提示词稳定性(缓存友好)
系统提示词在会话开始时一次性组装,对话过程中不再改动。这不是审美问题,而是经济问题。Anthropic 和 OpenAI 的提示词缓存都要求有稳定的前缀才能命中。会话中途切换工具集、重载记忆、换技能都会让缓存失效,导致成本翻 10 倍。默认情况下,所有改动都应推迟到 “下次会话”。
(3)渐进式披露
不要把所有技能、所有记忆、所有工具的完整文档都灌进系统提示词。只加载描述(Level 0)。当 agent 真的需要某个技能时,再让它拉取完整内容(Level 1)。只有当技能本身请求时,才加载被引用的文件(Level 2)。这就是为什么 Hermes 可以带着 47 个工具和几十个技能上路,同时还能不超出上下文限制。
(4)自注册优于集中式清单
工具和插件应该在导入时自己注册(registry.register(...)),而不是被加到一个手维护的 __all__ 列表里。新增工具 = 一个新文件,其他地方一行不用改。
(5)Profile 隔离
多个独立的 agent 实例可以共存——每个实例都拥有自己的目录(默认是 ~/.hermes/,可通过环境变量覆盖)。代码库里的每一个文件系统路径都要走 get_hermes_home()——绝不要硬编码 ~/.hermes。
(6)Agent 拥有自己的学习产物
技能不是人类编辑源码加进去的。Agent 在解决了一个非平凡任务后,通过一个叫 skill_manage 的工具自己写。记忆也不是人类整理的——agent 在每一轮之间编辑 MEMORY.md 和 USER.md。这就是那个闭环。
3. 高层架构

用大白话讲,三层:
- 第 1 层 — Surfaces: 人或系统如何跟 agent 对话(CLI、聊天平台、cron)。
- 第 2 层 — Agent 核心: 主循环 + 四个可插拔子系统(工具、技能、记忆、模型)。
- 第 3 层 — 执行后端: shell / 代码运行类工具实际执行的地方。今天在本地笔记本,明天在沙箱化的 Docker,生产环境跑在 Modal 云上。
4. Agent Loop🔄
这是最重要的一块。AIAgent 类本质上就是下面这个循环:
1. 接收输入 → 来自 CLI / gateway / cron / ACP / web
2. 构建系统提示词 → persona + memory + skills + tools (每个会话一次)
3. 解析 provider → 给选定模型用哪个 API key + endpoint
4. 调用模型 → 四种 API 模式之一 (根据 endpoint/model 自动判断):
chat_completions | codex_responses |
anthropic_messages | bedrock_converse
5. 解析响应
├─ 如果有 tool calls → 通过 registry 派发每一个 → 追加结果 → 回到 4
└─ 否则 → 最终的 assistant 消息 → 显示 → 持久化 → 结束
6. 持久化 → SQLite SessionDB (WAL 模式 + FTS5 索引)

几个不那么显而易见但很关键的细节:
- 迭代预算 — 比简单计数器要细腻得多。 一个线程安全的
IterationBudget被父 agent 及其派生出的所有子 agent 共享。execute_code在完成时会退还迭代次数,避免一个程序化的工具循环耗尽预算。预算耗尽时:注入一条警告消息(_budget_exhausted_injected),允许恰好再一次最终 API 调用(_budget_grace_call),然后强制做总结。中间没有任何警告——这是刻意为之,避免模型过早放弃。 - 推理内容是单独存储的,跟可见的 assistant 消息分开(OpenAI o 系列和 Anthropic 的 extended thinking 都会产生隐藏的 “reasoning” token)。把它们放在独立字段里——缓存有效性需要它们,但不应该展示出来。相关回调有:
stream_delta_callback、interim_assistant_callback、thinking_callback、reasoning_callback。 - 带状态的流式擦洗。
_stream_context_scrubber会剥离<memory-context>段,即使这些段跨越了多个 chunk 也能正确处理——别低估这件事的复杂度,标签跨越网络边界时各种小问题。 - 压缩,不是截断。 上下文满了的时候,
context_compressor会总结中间的轮次,而不是把它们丢掉。总结本身变成一条消息。有损是可以接受的;无损会直接 OOM。 - 中断。 工具调用过程中按 Ctrl-C 必须能干净地取消正在执行的工具,向历史追加一条 “用户中断” 的 tool result,然后把控制权交回来。不要杀掉整个循环——让 agent 看到中断并作出响应。
- 会话恢复。
--continue/--resume通过SessionDB.get_messages()加载历史。SQLite 的 WAL 模式 + 自定义的重试层(20–150 毫秒抖动,BEGIN IMMEDIATE)处理多进程写竞争。继续会话前会给用户展示一段回顾。
5. 系统提示词的组装

prompt_builder.build_system_prompt() 函数按这个顺序拼接以下各段:
- 人格(Persona) —
SOUL.md/DEFAULT_AGENT_IDENTITY。身份、口吻、价值观。 - 平台提示 —
PLATFORM_HINTS。告诉模型它现在跑在 CLI、Telegram、Slack 还是别的什么地方——这会改变格式化规则(CLI 里不用 MarkdownV2,Telegram 里不能嵌套代码块……)。 - 记忆指引 —
MEMORY_GUIDANCE。以 冻结快照 的形式把MEMORY.md和USER.md嵌入为一个块(用§分隔符隔开)。有大小上限(MEMORY 约 2200 字符,USER 约 1375 字符)。 - 会话搜索指引 —
SESSION_SEARCH_GUIDANCE。告诉 agent 它可以通过 FTS5 搜索过往会话,并给一个小例子。 - 技能指引 —
SKILLS_GUIDANCE。Level-0 技能索引,加上启发式的散文,提醒 agent 在解决困难任务后主动创建技能。 - 上下文文件 — 来自工作目录的
AGENTS.md和.hermes.md。 - 工具使用强制 —
TOOL_USE_ENFORCEMENT_GUIDANCE。关于并行调用、错误恢复等的硬规则。 - 工具 schema — 所有启用工具的 JSON schema。
然后 prompt_caching.py 插入缓存断点(Anthropic 用 cache_control: {type: ephemeral},其他 provider 各有等价物)。整个拼接出来的前缀就是可缓存区域。
冻结快照模式(这就是窍门)。 MEMORY.md 和 USER.md 只在会话开始时读一次,然后在余下整个会话中作为不可变内容嵌入系统提示词。Agent 在会话过程中依然可以往这些文件里写——但系统提示词不会变。结果:缓存在整个对话中都保持有效,新记忆在下次会话生效。跳过这一招,你的前缀缓存就完蛋了。
记忆安全扫描。 在注入之前,MEMORY/USER 内容会被扫描,检测提示词注入模式、外泄企图(引用环境变量的 curl/wget)、持久化后门以及不可见 Unicode。被污染的记忆文件是 agent 的朊病毒——必须防御性扫描。
关键规则: 1–8 节在整个会话中保持冻结。用户消息和工具结果只会被追加到历史中,不会进入系统提示词。
6. 工具系统🛠️
(1)自注册式注册表
一个中央的 tools/registry.py 暴露:
registry.register(
name="read_file",
toolset="filesystem",
schema={...JSON schema...},
handler=read_file_handler,
available=lambda ctx: True, # 准入判断函数
)
每个工具文件在模块导入时调用这个函数。注册表负责处理:
- 给系统提示词收集 schema。
- 当模型发出 tool call 时按名称派发。
- 可用性过滤(按用户、按平台、按工具集)。
- 错误包装 — handler 里抛出的任何异常都会被转换成 tool result,让模型可以看到并作出反应。永远不要让一个工具崩掉整个循环。
所有 handler 都返回 JSON 字符串,而不是 Python 对象。模型看到的永远只是文本。
(2)工具集(Toolsets)
工具按逻辑分组(filesystem、web、browser、code、mcp、vision、audio……)——Hermes 自带约 40+ 个工具(文档里有些地方写 “47 个内置工具”,有些地方写 “40+”;AGENTS.md 里写文件系统是规范来源,因为这个数量一直在变——你自己的版本里别硬编码数字)。用户按工具集而不是逐个工具地启用/禁用。被禁用的工具集完全不会出现在系统提示词里——既省 token,也防止模型甚至知道有这些工具。
(3)执行环境
跑 shell 命令或代码的工具,都通过一层环境抽象(tools/environments/):
| 后端 | 用途 |
|---|---|
local |
在你笔记本上开发用。最快,零隔离。 |
docker |
共享开发机。每个会话一个容器。 |
ssh |
远程 VM。把 VM 当作 agent 的 “电脑”。 |
daytona / modal |
给生产用的无服务器沙箱。自动开机。 |
singularity |
HPC 集群。 |
同一个工具,爆炸半径不同。Agent 自己并不知道——只是底下的环境不同。
(4)Agent 级工具
少数几个工具(todo_*、memory_*、skill_manage、skills_list、skill_view)会在通用工具派发之前被拦截,由 agent 自己处理,因为它们修改的是 agent 自身的状态(记忆、技能、待办列表)而不是外部世界。这一类要保持小而明确。
(5)MCP 集成
Model Context Protocol 服务器可以作为额外的工具来源插进来。Hermes 把每个 MCP server 当作一个虚拟工具集,允许用户过滤其中的单个工具,并通过同一套 registry 派发调用。这样你就能不写一行集成代码,得到一长串的集成(GitHub、Slack、Linear……)。
(6)工具审批与安全(分层防御)
Shell 类工具很危险。Hermes 分了四层:
- Tirith — 一个外部的 Rust 扫描器,带自动安装 + SHA-256 校验。它能检测同形字 URL、终端注入攻击(用 ANSI 转义隐藏命令)以及已知的危险模式。
- 正则危险命令检测 — 在 规范化(大小写不敏感,空白压缩)的命令串上跑,这样攻击者就没法用
RM -RF绕开了。 - 智能审批 — 由一个 LLM 对每条命令进行风险评级。低风险自动通过;中/高风险阻塞,等人类批准。
- 审批作用域 — 人类批准时可以选 本次 / 本会话 / 永久。信任会累积,不用每次都问。
当 agent 跑在消息网关上需要审批时,它用一个 threading.Event 阻塞,等人在聊天里回复。/yolo 命令可以让受信任的会话完全跳过审批。沙箱后端会自动跳过审批(Docker/Modal 沙箱本身就是安全边界,再问一遍只是徒增摩擦)。

7. 技能系统
(1)什么是 skill
一个 skill 就是一个带 YAML frontmatter 的 markdown 文档,教 agent 怎么把某件事做好。不是代码。不是配置。是 agent 阅读的操作手册。
---
name: deploy-staging
description: 把当前分支通过 Vercel 推到 staging 并校验健康状态。
version: 1.2.0
platforms: [macos, linux]
requires_toolsets: [shell, web]
fallback_for_toolsets: []
required_environment_variables: [VERCEL_TOKEN]
tags: [deploy, vercel]
category: devops
---
## 何时使用
用户说 "ship"、"deploy to staging"、或 "preview this branch"。
## 步骤
1. 运行 \`git status\` — 如果有未提交改动则中止。
2. 运行 \`vercel --token=$VERCEL_TOKEN\`。
3. 轮询 \`/healthz\` 直到返回 200 或 60 秒超时。
4. 汇报 preview URL。
## 陷阱
- 不要从 \`main\` 部署——只部署 feature 分支。
- 构建失败时,用 \`vercel logs <deployment>\` 抓日志。
## 验证
healthz endpoint 返回 \`{"status":"ok"}\`。
(2)skill 存放在哪里
~/.hermes/skills/
├── devops/deploy-staging/
│ ├── SKILL.md ← 上面那个文件
│ ├── references/ ← skill 可以拉取的额外文档
│ ├── templates/ ← 文件模板
│ ├── scripts/ ← agent 可运行的辅助脚本
│ └── assets/ ← 图片等
├── .hub/ ← 从 skills hub 安装而来的
└── .bundled_manifest ← Hermes 自带的
(3)渐进式披露(3 个层级)
这是 token 用量保持理智的关键:
| 层级 | 加载什么 | 何时 |
|---|---|---|
| 0 | name、description、category | 始终加载——在系统提示词中 |
| 1 | 完整的 SKILL.md 内容 | agent 决定使用该技能时 |
| 2 | references/、scripts/ 里的文件 |
skill 正文中说 “见 references/foo.md” 时 |
Agent 通过调用 read_skill(或等价工具)从 L0 升级到 L1 再到 L2。

(4)触发方式
技能被激活的三种方式:
- 斜杠命令 — 用户输入
/deploy-staging please ship #123。 - 自然语言 — “deploy this to staging”;agent 基于 L0 描述做匹配,拉取 L1。
- 程序化 — cron 任务显式挂载技能。
(5)条件激活
Frontmatter 字段控制可见性:
platforms: [linux]— 在 macOS 上隐藏。fallback_for_toolsets: [web]— 仅当没有高级 web 工具启用时可见(比如一个 DuckDuckGo 技能,只在 Brave Search 没配置时顶上)。requires_toolsets: [shell]— 如果 shell 工具被禁用则隐藏。
这样技能目录就会随着部署环境自动适配。
(6)自我进化:skill_manage 工具
Agent 用两个互补的工具:
- 读取路径:
skills_list(浏览 Level-0 索引)和skill_view(升级到 Level-1/2 内容)。 - 写入路径:
skill_manage,一个带子操作的 meta-tool:
| 动作 | 效果 |
|---|---|
create |
从零创建新技能 |
patch |
精准文本替换(更新时首选) |
edit |
全量重写 |
delete |
删除技能(仅限用户/agent 创建的技能 — 自带技能不能删) |
注意:技能内部的文件管理(references/、scripts/)走通用的 write_file / remove_file 工具,作用域限定在该技能目录内。
系统提示词中的 SKILLS_GUIDANCE 块显式地推动 agent在以下情况后创建技能:
- 解决了一个用了 5+ 次工具调用的任务。
- 找到了一个不显然的变通方案。
- 发现了一个可能重复使用的工作流。
来自 hub 的技能安装只能由用户发起(安全考虑)。Agent 永远不会自己安装未受信任的技能——它只能通过 skill_manage create 从自己的经验中创建。
这就是那个闭环学习。Agent 边工作边写自己的剧本。

(7)Skills hub 与共享
技能是可移植的 markdown ——天然适合分享。Hermes 集成了多个来源(official/、skills-sh/、github/、well-known/、url、clawhub、lobehub)。安装时每个技能都会被安全扫描,检测提示词注入、数据外泄和破坏性命令,然后才被信任。信任层级:builtin > official > community。
格式遵循开放的 agentskills.io 标准——这意味着为 Hermes 写的技能可以在其他兼容的 agent 里运行。
8. 记忆系统
三个独立机制协同工作(所谓 “3 层” 是教学上的简化——在代码里它们是正交的):

(1)冻结快照式持久记忆
两个 markdown 文件,都由 agent 自己维护:
MEMORY.md— 事实。“项目每周二发版。” “测试 DB 密码在 1Password vault X 里。” (约 2200 字符上限)USER.md— 用户画像。“偏好简短回答。” “资深 Go 工程师,刚开始接触 React。” (约 1375 字符上限)
一个 MemoryStore 在会话开始时读一次,然后把它们作为一个不可变块嵌入系统提示词(由 § 分隔)。Agent 在会话过程中可以往这两个文件里写(写入会落盘),但系统提示词里的那份副本要到下次会话才会更新。这就是保持前缀缓存有效的关键。
(2)通过 SessionDB 进行跨会话回忆
一个 SessionDB(SQLite,WAL 模式,FTS5 全文索引)存储每一轮过往对话。需要时,agent 用 session_search 工具查询;一个 LLM 总结器把命中的内容压缩成一段适合放进上下文的文字。多进程写竞争用 BEGIN IMMEDIATE + 自定义重试循环(20–150 毫秒抖动)来处理。
(3)可插拔的 provider(Honcho / mem0 / supermemory)
这是个替换式机制,不是额外加一层。一个 ABC 基类(MemoryProvider,在 agent/memory_provider.py);编排逻辑在 agent/memory_manager.py。生命周期钩子:prefetch()(模型调用前)、sync_turn()(一轮之后)、shutdown()。
要关心的 provider 旋钮:
- 召回模式:
hybrid/context/tools。Tools 模式让模型决定何时查询;context 模式每轮都注入相关记忆。 - 写入频率:
async/turn/session/ 数值(每 N 轮)。
Honcho 的 “dialectic” 值得说明一下,因为它听起来玄乎,其实并不:它跑三轮顺序推理 — Initial Assessment → Self-Audit → Reconciliation — 深度由 dialecticDepth(1–3)控制。本质上就是链式自我批判,用于生成更高质量的用户模型。
任何时候只有一个 provider 在跑。挑一个适合你场景的抽象(要深度用户建模选 Honcho,要向量召回选 mem0/supermemory,文件 + FTS5 够用就别上)。
9. 插件系统
一个 PluginManager 从三个地方发现插件:
~/.hermes/plugins/(用户级)./.hermes/plugins/(项目级)- pip entry points(
hermes.plugins)
每个插件定义一个 register(ctx) 函数,可以挂载到生命周期事件:
pre_tool/post_toolpre_llm/post_llmsession_start/session_end
……可以注册新工具、新 CLI 命令,或者替换记忆 provider。
铁律: 插件绝不修改核心文件。如果一个插件需要框架没暴露的东西,那框架应该长出一个通用的钩子——而不是开个特例 import。这样插件接口才能保持稳定。
(1)COMMAND_REGISTRY 模式(值得借鉴)
hermes_cli/commands.py 里有一个常量 COMMAND_REGISTRY,是所有斜杠命令的唯一来源。从这一个结构里,代码库自动派生出:
- CLI 派发
- Gateway 钩子(这样
/skill foo在 Telegram 里也能用) - Telegram inline 菜单条目
- Slack slash 子命令
- prompt_toolkit 自动补全
/help文本
新增一个斜杠命令 = 一个新的 CommandDef 条目 + 一个 handler。零散落的修改。 这跟工具注册表是同一个模式,只是应用在 UI 命令上。给自己的项目偷一个吧——这就是 Hermes 表面积可以扩展、但维护成本却不爆炸的秘诀。
(2)把主题做成数据
~/.hermes/skins/ 下的 YAML 文件(从 default 继承)。一个 YAML 控制 18 种命名颜色、spinner 的脸和动词、agent 名字和问候/告别语、提示符号、工具 emoji、带 Rich markup 的 ASCII banner。10 个内置皮肤(default、daylight、mono、poseidon、charizard……)。Hermes Mod 还自带一个带实时预览和图像→ASCII 转换的 web 编辑器。
架构上的启发:品牌存在 YAML 里,不存在代码里。 用户可以不动 Python 就 fork 一套外观。对一个用户一坐就是几小时的 agent 来说,这件事比你想象的更重要。
(3)多模态与流式
- 视觉:
vision_analyze工具。Anthropic 通过_anthropic_image_fallback_cache做图像转文本的兜底缓存(模型本身看不懂图像时,缓存能避免重复描述同一张图)。 - 音频输出:
text_to_speech工具。 - 音频输入: 输入侧的语音备忘录转写。
- 浏览器工具: 注入多模态上下文(截图 + DOM + 提取的文本)。
- 流式:
_stream_callback、_current_streamed_assistant_text,加上有状态的_stream_context_scrubber——后者即使在 chunk 边界上也能稳妥地剥离<memory-context>段。
(4)RL / Atropos 训练集成(environments/)
对 Nous Research 来说,这某种程度上才是项目的重点,而不是边角料。environments/ 目录把 Hermes 包装起来用于强化学习训练:
HermesAgentBaseEnv— 抽象工具解析和沙箱接线。HermesAgentLoop— 用 RL rollout 能驱动的方式跑工具调用循环。ToolContext— 把沙箱暴露给奖励函数(这样奖励函数可以 grep 文件系统来验证 agent 是否真的完成了工作)。resize_tool_pool— 防止并行 rollout 时线程池死锁。- 两阶段训练流水线:
- 阶段 1: VLLM/SGLang 原生 tool-call 解析。
- 阶段 2: 原始 token 解析——Hermes 的 XML 风格 tool 标签和 DeepSeek 的 Unicode 分隔符需要这一阶段,用
ManagedServer。
- 阶段 2: 原始 token 解析——Hermes 的 XML 风格 tool 标签和 DeepSeek 的 Unicode 分隔符需要这一阶段,用
- 阶段 1: VLLM/SGLang 原生 tool-call 解析。
- 三层 tool result 预算: 每个工具截断 → 沙箱溢出附带预览 → 每轮预算。没有这套,一个
ls /就能炸掉训练 rollout 的上下文窗口。 - 预集成 benchmark: TerminalBench 2.0、YC-Bench、WebResearch。
你的 v1 大概率用不上这些。知道这些钩子在那里就好——以后真要拿自己 agent 的轨迹来微调模型,你能找到入口。
10. Surfaces
同一个 AIAgent 驱动六个不同的 surface。每一个都是一层薄薄的适配器,不是重新实现。

(1)CLI(经典)cli.py(约 11k 行)。基于 Rich 的面板、用 prompt_toolkit 做输入加自动补全、动画 spinner(KawaiiSpinner)、API 调用时的活动 feed。
(2)TUI(hermes --tui)— 真正新颖
不只是更花哨的 CLI。 架构:
- 前端: Node.js + React Ink。
- 后端: Python
tui_gateway/server.py。 - 通信格式: 基于 stdio 的换行分隔 JSON-RPC 2.0。
Python 这边把 print 重定向到 stderr,让 stdout 保持纯净给协议用。一个持久子进程跑斜杠命令;慢 handler 走 _SlashWorker(一个 ThreadPoolExecutor),这样中断响应才能保持灵敏。亮点功能:盲文 spinner 流式展示思考链、ToolTrail 树状可视化、虚拟历史视窗(只渲染可见行)、鼠标选择。
来自 AGENTS.md 的设计规则: *不要在 React 里重新实现聊天界面。*对话记录、输入框、斜杠命令行为属于嵌入的 TUI。侧边栏和检查器没问题——替换式视图不行。
(3)Gateway(消息平台)
Telegram、Discord、Slack、WhatsApp、Signal。每个适配器:
- 连接到平台(websocket / long-poll / webhook)。
- 收到消息时:对用户做鉴权,推导出稳定的
session_key,从 SessionDB 找会话,用这段历史实例化一个AIAgent。 - 调用
agent.run_conversation()。 - 格式化并发回响应(Telegram 的 MarkdownV2 vs Discord 的 markdown 风格 vs Slack 的 mrkdwn —— 这些都活在适配器里)。
(4)ACP(Agent Client Protocol)— 面向 AI 原生编辑器
ACP 是 Zed 和正在兴起的 VS Code 集成跟 agent 对话所用的标准协议。Hermes 实现了 HermesACPAgent。ACP 会话绑定到编辑器的 cwd,并在同一个共享的 SessionDB 中持久化。Hermes 的工具映射到 ACP 的语义类型(比如 read_file → read),IDE 可以注册 MCP server,agent 就会把它们当作额外的工具集看见。
(5)Web UI(hermes web)web/ 里的 React SPA + hermes_cli/web_server.py 里的 FastAPI。标签页:Status、Sessions(FTS5 搜索 UI)、Config(表单 + 原始 YAML)、Cron、Skills。安全:临时 session token、DNS rebinding 防护、CORS、限流。EN/中文 本地化。
(6)Cron 调度器(~/.hermes/cron/)
不是 APScheduler。 一个自定义调度器,在 gateway 进程内的后台线程上跑一个 60 秒的 tick() 循环。任务以 JSON 形式存在 ~/.hermes/cron/jobs.json 里(不是 SQLite)。输出落到 ~/.hermes/cron/output/{job_id}/{timestamp}.md。
任务定义支持:
- 间隔(
every 30m)、5 字段 cron、一次性持续时间、ISO 时间戳。 - 一个
prompt字段(要发送给 agent 的用户消息)。 - 一个可选的
skills列表,在执行前附加(这样一个 “review-PRs” 的 cron 任务可以预加载一个pr-review技能)。 - 投递目标:
local(只写入)、origin(回到任务创建的地方)、platform:chat_id(发到指定的 Telegram/Slack 频道)。
每个 tick:创建一个全新、无历史的 AIAgent,加载附加的技能,执行 prompt,投递输出,更新任务状态。
(7)批处理 runner(训练数据流水线)
repo 根目录下的两兄弟:
batch_runner.py— 一个BatchRunner,跑在multiprocessing.Pool上,每个 worker 一个隔离的AIAgent。toolset_distributions.py按独立的包含概率为每个 prompt 采样工具集。checkpoint.json的 checkpoint 是按 prompt 文本 而不是索引来键的(这样 prompt 列表的编辑不会让 checkpoint 失效)。输出按 HuggingFace 的轨迹格式;推理通过<REASONING_SCRATCHPAD>标签或原生 thinking token 检测——没有推理的轨迹会被丢弃。mini_swe_runner.py— 一个用于 SWE 风格 benchmark 的兄弟 runner。
Nous 就是用这套从真实的 agent 跑流中生成训练数据的。
11. Profile 与多实例
想在同一台机器上同时跑一个 “个人” agent 和一个 “工作” agent,又不希望它们的记忆串味?用 profile。
实现简单得可笑,但时机至关重要:
- 每个 profile 拥有自己的
HERMES_HOME目录。 hermes_cli/main.py里的_apply_profile_override()在任何其它模块导入运行之前就设定HERMES_HOME。如果你在导入之后才设,那些在 import 时读取路径的模块就会用到错的 home。- 每一个路径查找都走
get_hermes_home()。代码库里任何地方硬编码的~/.hermes都会破坏 profile 隔离。
要做对的几件事:
- 测试必须同时 mock
Path.home()和环境变量——只 mock 其中一个会导致测试莫名其妙地抖动。 - Gateway 适配器要拿一个 per-profile 的 token 锁,避免两个 profile 同时想消费同一个 Telegram bot token。
- Honcho identity(以及其它记忆 provider 的 ID)都是 profile 作用域的——别跨 profile 共享,否则用户模型会互相污染。
12. 提示词缓存💰
这是你的 agent 在生产环境里到底是便宜还是昂贵的最大单一原因。

要做:
- 每个会话只构建一次系统提示词。
- 插入 provider 专属的缓存断点(Anthropic:在前缀里最后一条静态消息上加
cache_control: {type: "ephemeral"})。 - 对记忆使用冻结快照模式:会话开始时读一次 MEMORY/USER 文件,然后即使磁盘上变了,也要不可变地嵌入。
- 把配置变更(“开/关某工具集”、“换模型”)默认推迟到下次会话。修改状态的斜杠命令可以接受一个可选的
--now标志,但默认要延迟。
别做:
- 会话中途重新加载记忆。
- 会话中途增删工具。
- 因为用户"换了话题"就改系统提示词。
- 让系统提示词依赖当前时间、随机 ID 或任何按轮变化的东西。
缓存命中的前缀的读取成本大约只有写入成本的 1/10。前缀稳定的话,一段 10 轮的对话总成本约为单轮的 1.5 倍。前缀不稳定的话,是 10 倍。
13. 参考资料
- Repo:github.com/nousresearch/hermes-agent
- 文档:hermes-agent.nousresearch.com/docs
- 架构页:hermes-agent.nousresearch.com/docs/developer-guide/architecture
- Skills 格式规范:agentskills.io
- Skills hub:agentskills.io
- DeepWiki 概览:deepwiki.com/NousResearch/hermes-agent
AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。
更多推荐



所有评论(0)