hermes-agent 深度拆解:当 AI Agent 决定「记住你是谁」——一个 130K Star 的开源框架如何用 Personal Memory 和闭环学习重新定义自进化 Agent 的终极形态
引言:Agent 不该每次都失忆
2026 年,AI Agent 已经从概念走向生产。但你有没有遇到过这种尴尬:昨天刚告诉 Agent 你的代码规范、项目偏好、沟通习惯,今天它又全忘了?每次对话都像第一次见面,你需要重新铺垫上下文、重申需求、重复纠正。
这不是 Agent 的 bug,而是架构层面的根本缺陷。
绝大多数 Agent 框架的思路是:让 LLM 更好地调用工具。Nous Research 的 hermes-agent 走了一条完全不同的路:让 Agent 越用越聪明。
2026 年 2 月发布,两个月内 GitHub Star 冲到 130K,贡献者超过 240 人。hermes-agent 用代码证明了一件事:AI Agent 可以拥有长期记忆、可以自我学习、可以真正「认识」你。
本文从源码出发,把 hermes-agent 的架构拆开来看。不是功能介绍,而是分析它为什么这样设计,以及这些设计决策背后的取舍。
一、项目定位:从「工具调用器」到「自进化智能体」
1.1 传统 Agent 的困境
市面上大部分 Agent 框架(LangChain、CrewAI 等)的核心抽象是:Agent = LLM + Tools。LLM 负责理解意图,Tools 负责执行操作。这个模型在单次任务中表现不错,但一旦涉及多轮交互、长期项目跟进,就暴露了致命短板:
- 无记忆:每次对话都是白纸一张,Agent 不认识你
- 无学习:同样的错误反复犯,不会从历史中吸取教训
- 无个性:所有用户拿到的是同一个 Agent,没有差异化
hermes-agent 的官方定位是 self-improving AI agent——自进化 AI Agent。这个定位直接决定了整个架构的设计取舍。
1.2 闭环学习:五阶段循环
hermes-agent 的核心创新是把「学习」做成了原生能力,不是事后打补丁,而是从 Day 1 就融入了核心架构。
完整的闭环学习包含五个环节:
完成任务 → 策划记忆 → 创建 Skill → Skill 自改进 → FTS5 召回 → 用户建模 → (循环)
具体来说:
- 策划记忆:任务完成后,Agent 自主判断什么值得记住
- 创建 Skill:识别重复模式,自动生成 Markdown 格式的 Skill 文件
- Skill 自改进:现有 Skill 失败时自动优化
- FTS5 召回:按需检索历史对话(SQLite 全文索引)
- 用户建模:从行为推断偏好
这套机制对应认知科学的三种记忆类型:
| 记忆类型 | hermes-agent 实现 | 存储位置 |
|---|---|---|
| 情景记忆 | 会话历史 + FTS5 索引 | SQLite |
| 语义记忆 | MEMORY.md 持久事实 | 文件系统 |
| 程序性记忆 | Skill 文件 | ~/.hermes/skills/ |
二、核心架构:源码级拆解
2.1 目录结构与职责划分
先看目录结构,快速理解系统的职责划分:
hermes-agent/
├── run_agent.py # Agent 主循环核心
├── tools/ # 40+ 工具实现
├── toolsets.py # Toolset 组合系统
├── agent/
│ ├── prompt_builder.py # System Prompt 组装器
│ ├── memory_manager.py # 记忆管理(双 Provider)
│ ├── context_engine.py # 上下文引擎(可插拔)
│ └── context_compressor.py # 轨迹压缩器
├── hermes_state.py # SQLite 状态存储 + FTS5
├── gateway/run.py # 多平台消息网关
├── skills/ # 内置 Skill 库(24 个分类)
├── plugins/ # 插件系统
├── hermes_cli/ # CLI 接口(51 个模块)
└── environments/ # 执行环境后端
几个关键的设计决策:
- Agent 循环和工具执行在同一进程:通过
run_agent.py中的AIAgent类管理整个生命周期,没有用微服务 - 工具自注册模式:每个工具文件在模块级别调用
registry.register()声明自己,不需要中心化的注册表 - 上下文压缩可插拔:
ContextEngine是抽象基类,可以替换实现 - 状态存储用 SQLite + WAL:单文件数据库,支持多读者 + 单写者
- MCP 双向支持:既能作为 MCP 客户端连接外部工具服务器,也能作为 MCP 服务端被 Cursor、VS Code 等接入
2.2 Profile 系统:多实例隔离
v0.6.0 引入了多实例 Profile,每个 Profile 拥有独立的:
- 配置
- 记忆库
- 会话历史
- Skill 集合
- 工具权限
这意味着你可以在同一台机器上跑多个独立的 Agent 实例,互不干扰。比如一个用于工作项目,一个用于个人自动化。
三、Agent 循环深度拆解
run_agent.py 中的 AIAgent 类是整个系统的心脏。它管理对话流、工具执行、响应处理的全流程。
3.1 基本循环
Agent 循环的基本流程:
接收用户消息
→ 构建请求(含 system prompt + 记忆 + 上下文)
→ 调用 LLM
→ 解析响应
→ 如果包含工具调用则执行工具
→ 把工具结果加入上下文
→ 再次调用 LLM
→ 直到 LLM 不再请求工具调用
→ 返回最终响应
3.2 迭代预算控制
Agent 循环不是无限运行的。IterationBudget 类实现了一个线程安全的迭代计数器:
class IterationBudget:
"""线程安全的迭代预算控制"""
def __init__(self, max_iterations: int):
self._max = max_iterations
self._count = 0
self._lock = threading.RLock()
def decrement(self) -> bool:
"""减少预算,返回 True 表示还有余量"""
with self._lock:
if self._count >= self._max:
return False
self._count += 1
return True
@property
def exhausted(self) -> bool:
with self._lock:
return self._count >= self._max
预算配置:
- 父 Agent 默认 90 次迭代
- 子 Agent 默认 50 次迭代
这个设计解决了一个很实际的问题:Agent 有时候会陷入死循环,反复调用同一个工具。迭代预算到了就强制停止,防止 token 烧穿。
3.3 并行工具执行
_should_parallelize_tool_batch() 方法把工具分成三类:
| 分类 | 策略 | 典型工具 |
|---|---|---|
_NEVER_PARALLEL_TOOLS | 永远不并行 | clarify(需要用户交互) |
_PARALLEL_SAFE_TOOLS | 只读安全,可并行 | web_search, read_file |
_PATH_SCOPED_TOOLS | 路径隔离,条件并行 | read_file, write_file, patch |
最大并发工作线程数硬编码为 8(_MAX_TOOL_WORKERS)。
这个设计说明团队对 Agent 的实际使用场景做过深入思考。比如 web_search 和 read_file 都是纯读操作,并行执行完全安全。但 write_file 和 patch 操作同一文件时就有冲突风险,所以需要路径隔离检查。
3.4 中断与转向机制
Agent 在执行过程中可能会收到用户的新指令。hermes-agent 的处理方式是 _interrupt_requested + _pending_steer 双标志位设计:
- 不打断当前正在执行的工具:等工具批次完成后再处理
- 注入转向指令:把用户的新需求作为
_pending_steer注入下一轮对话
这个选择很务实。如果强行中断正在执行的 write_file,可能导致文件写了一半。等工具批次完成再转向,保证了操作的原子性。
四、工具系统:自注册 + Toolset 组合
4.1 ToolRegistry 自注册模式
工具系统的核心在 tools/registry.py。它采用了一个很优雅的自注册模式:
# tools/registry.py 中的 ToolEntry 结构(简化)
class ToolEntry:
name: str # 工具名
toolset: str # 所属工具集
schema: dict # JSON Schema
handler: callable # 处理函数
check_fn: callable # 检查函数
requires_env: bool # 是否需要执行环境
is_async: bool # 是否异步
description: str # 描述
emoji: str # 显示图标
每个工具文件在模块级别调用 registry.register(),声明自己的 schema、handler、toolset 归属。注册表用 threading.RLock() 保证线程安全,读取时做快照。
发现机制也很有意思:discover_builtin_tools() 通过 AST 分析检测哪些 .py 文件包含 registry.register() 调用,不需要手动维护工具列表。
4.2 Toolset 组合系统
toolsets.py 实现了一个组合式工具集系统。Toolset 之间可以互相 includes,支持递归解析。
核心工具列表 _HERMES_CORE_TOOLS 包含 63 个工具,所有平台共享:
| 类别 | 工具举例 |
|---|---|
| Web | web_search, web_extract |
| 终端 | terminal, process |
| 文件 | read_file, write_file, patch, search_files |
| 视觉 | vision_analyze, image_generate |
| 技能 | skills_list, skill_view, skill_manage |
| 浏览器 | browser_navigate 等 11 个浏览器工具 |
| 规划 | todo, memory |
| 代码执行 | execute_code, delegate_task |
| 定时任务 | cronjob |
| 其他 | send_message, Home Assistant, session_search, clarify |
平台特定 toolset 如 hermes-cli、hermes-telegram、hermes-discord 等有 20 多个。hermes-gateway 是所有平台工具的并集。
这种组合式设计的好处是:新接入一个平台时,只需要定义该平台特有的工具,然后 includes 核心工具集就行。比如新增一个飞书适配器,只需要定义飞书的消息发送、消息格式化等平台特有工具,然后引用 hermes-gateway 的基础工具集。
4.3 MCP 集成
hermes-agent 既支持作为 MCP 客户端连接外部 6,000+ 应用,也能作为 MCP 服务端被 Cursor、VS Code 等 IDE 接入。MCP 工具通过动态注册机制集成到 ToolRegistry 中。
# MCP 工具动态注册示例
async def register_mcp_tools(server_config: dict):
"""连接 MCP 服务器并注册其工具"""
client = MCPClient(server_config)
tools = await client.list_tools()
for tool in tools:
registry.register(
name=f"mcp_{server_config['name']}_{tool.name}",
toolset="mcp",
schema=tool.input_schema,
handler=client.call_tool,
description=tool.description
)
五、记忆与学习闭环:架构的核心差异
这是 hermes-agent 和其他 Agent 框架拉开差距的地方。
5.1 双文件记忆架构
agent/memory_manager.py 中的 MemoryManager 采用双 Provider 架构:一个内置 Provider(管理 MEMORY.md 和 USER.md)+ 最多一个外部 Provider(如 Honcho)。
两个记忆文件的分工很明确:
MEMORY.md:Agent 的个人笔记。记录环境事实、项目约定、工具的使用技巧USER.md:Agent 对用户的认知。记录偏好、沟通风格、期望
# 记忆注入示例
memory_context = f"""
<memory-context>
{memory_manager.get_snapshot()}
</memory-context>
"""
# 注入到 system prompt 中
记忆条目之间用 §(section sign)分隔。用一个不太常见的字符做分隔符,降低了和正文内容冲突的概率。
5.2 冻结快照模式
这个设计解决了一个很实际的问题:系统提示的稳定性。
会话开始时,MemoryManager 把当前的记忆内容作为快照注入系统提示。之后整个会话期间,系统提示保持不变——中途写入的记忆只更新磁盘文件,不刷新系统提示。
# 冻结快照模式的实现思路
class MemoryManager:
def start_session(self) -> str:
"""会话开始时获取记忆快照"""
snapshot = self._load_memory_snapshot()
# 快照注入后,整个会话期间不再刷新
self._session_snapshot = snapshot
return snapshot
def update_memory(self, key: str, value: str):
"""中途更新只写磁盘,不刷新会话快照"""
self._write_to_disk(key, value)
# 不更新 self._session_snapshot
这样做的好处是保持 前缀缓存有效。如果每次记忆更新都刷新系统提示,KV cache 就会失效,每次请求都要重新计算前面所有 token 的 KV 对。冻结快照让前缀缓存命中率大大提高。
这是一个典型的读写分离思路:读路径(会话开始时的快照注入)和写路径(中途更新磁盘文件)完全解耦。代价是 Agent 在一个会话内对记忆的修改,需要等到下一个会话才能被读取。对于大多数使用场景来说,这个延迟是可以接受的。
5.3 Skill 系统
Skill 文件存放在 ~/.hermes/skills/ 目录下,是标准的 Markdown 文件。
Skill 的创建和改进流程:
- Agent 完成一个复杂任务后,自动识别是否包含可复用的模式
- 如果有,生成 Markdown 格式的 Skill 文件,包含触发条件、执行步骤、注意事项
- 使用 Skill 时如果发现问题(过时、报错),立即 patch 更新
PromptBuilder 还实现了两级缓存(LRU + 磁盘快照)来加速 Skill 索引的读取。
5.4 会话搜索:FTS5 全文检索
hermes_state.py 中的 SessionDB 基于 SQLite FTS5 实现跨所有会话消息的全文搜索。
搜索流程:
用户提问 → Agent 判断是否需要历史信息
→ 调用 session_search(FTS5 召回)
→ LLM 总结召回结果
→ 结合 MEMORY.md/USER.md 的快照
→ 形成完整的记忆上下文
这是一个多级检索的设计,不同类型的记忆走不同的通道。
# FTS5 搜索示例
class SessionDB:
def search(self, query: str, limit: int = 10) -> list[dict]:
"""全文搜索历史会话"""
sql = """
SELECT messages.*, rank
FROM messages
JOIN messages_fts ON messages.id = messages_fts.rowid
WHERE messages_fts MATCH ?
ORDER BY rank
LIMIT ?
"""
return self.conn.execute(sql, (query, limit)).fetchall()
六、上下文管理:可插拔压缩引擎
6.1 ContextEngine 抽象架构
agent/context_engine.py 定义了抽象基类 ContextEngine,agent/context_compressor.py 提供了默认实现 ContextCompressor。
可插拔设计意味着你可以实现自己的上下文管理策略,比如基于 RAG 的检索增强、基于重要性评分的选择性保留等。
为什么要把上下文管理做成可插拔?因为不同使用场景对上下文的处理策略差异很大:
- 编程场景:需要保留完整的代码变更历史
- 对话场景:需要保留情感上下文
- 数据分析场景:需要保留中间结果
一个通用的压缩策略很难同时满足这些需求。
6.2 压缩策略详解
ContextCompressor 的压缩策略:
| 参数 | 默认值 | 作用 |
|---|---|---|
threshold_percent | 0.75 | 上下文使用率超过 75% 时触发压缩 |
protect_first_n | 3 | 保护前 3 条消息(通常是 system prompt + 用户初始请求) |
protect_last_n | 6 | 保护后 6 条消息(最近的对话上下文) |
压缩的具体流程:
- 保护头部和尾部:前 N 条和后 M 条消息不动
- 中间部分用辅助模型总结:不是简单截断,而是让另一个 LLM 生成结构化摘要
- 总结模板:包含已解决的问题、待处理的事项、活跃任务
- 工具输出裁剪:对工具返回的长文本做前置过滤,减少总结的 token 消耗
- 按比例分配 token 预算:中间部分的每段对话按比例分配总结的 token 预算
6.3 安全扫描
agent/prompt_builder.py 的 _scan_context_content() 检测注入攻击。
上下文文件的优先级是 .hermes.md > AGENTS.md > CLAUDE.md > .cursorrules。安全扫描会检查这些文件中是否包含恶意指令,防止 prompt injection。
七、子 Agent 委托机制
tools/delegate_tool.py 实现了子 Agent 委托机制,允许主 Agent 生成独立的子 Agent 实例来并行处理任务。
7.1 隔离设计
子 Agent 的隔离做得比较彻底:
- 无父历史:子 Agent 看不到父 Agent 的对话历史
- 独立终端会话:子 Agent 有自己的终端环境
- 工具限制:
DELEGATE_BLOCKED_TOOLS列表禁止子 Agent 使用特定工具
被禁止的工具包括:
- 递归委托(防止子 Agent 再生孙 Agent)
- 用户交互类工具(子 Agent 不应该直接和用户对话)
- 记忆写入工具(防止子 Agent 污染主 Agent 的记忆)
7.2 并行与深度控制
# 子 Agent 关键配置
MAX_DEPTH = 1 # 默认扁平,最多可配到 3
MAX_CONCURRENT = 3 # 最多 3 个并行子 Agent
并行执行通过 ThreadPoolExecutor 实现。多个子任务可以同时运行。
深度控制有两层含义:
- 委托深度:
_delegate_depth跟踪委托链的长度,默认MAX_DEPTH=1意味着子 Agent 不能再委托 - 角色模式:
leaf(叶节点,只能执行)或orchestrator(编排者,可以再次委托)
说实话,子 Agent 并发最多 3 个这个限制,社区里有人吐槽过。对于复杂的并行场景,3 个子 Agent 确实不够用。但从架构安全的角度看,限制并发数可以避免资源爆炸——每个 Agent 都有自己的终端会话和上下文,3 个就已经意味着 3 倍的资源消耗。
八、多平台网关架构
gateway/run.py 实现了一个统一的消息网关,单一进程处理所有平台消息。
8.1 支持的平台
网关支持 17+ 个平台:
Telegram、Discord、Slack、WhatsApp、Signal、Email、SMS、Home Assistant、Mattermost、Matrix、DingTalk(钉钉)、Feishu(飞书)、WeChat(微信)、WeCom(企业微信)、QQ、BlueBubbles、Webhook。
8.2 Agent 缓存策略
网关用 LRU 缓存 + 空闲 TTL 淘汰来管理 Agent 实例:
- 缓存容量:128 个 Agent 实例
- 空闲淘汰:1 小时无活动自动清理
这个设计是为了平衡内存使用和响应速度。Agent 实例占用内存不小(需要维护对话历史、工具状态等),但每次创建新实例开销也大。LRU + TTL 是一个合理的折中。
8.3 跨平台会话
同一个 Agent 可以同时服务多个平台——你可以在 Telegram 上开始一个对话,然后在 Discord 上继续。不同平台的消息格式差异(Telegram 的 Markdown V2、Discord 的 Embed、Slack 的 Block Kit)在网关层统一处理,Agent 核心不需要关心这些细节。
九、状态持久化:SQLite + FTS5
9.1 技术选型
- SQLite + WAL 模式:支持多读者 + 单写者,适合网关多平台并发场景
- FTS5 全文搜索:跨所有会话消息的快速文本搜索
- Schema 版本控制:当前 v8,自动迁移
9.2 写竞争处理
SQLite 在高并发写入时容易出现锁竞争。SessionDB 的处理方式是随机抖动重试:15 次重试,每次等待 20-150ms 的随机时间。这个随机抖动可以避免多个写请求同时重试导致的护航效应(convoy effect)。
# 写竞争处理示例
async def _write_with_retry(self, sql: str, params: tuple) -> None:
"""带随机抖动的写重试"""
for attempt in range(15):
try:
self.conn.execute(sql, params)
self.conn.commit()
return
except sqlite3.OperationalError as e:
if "locked" in str(e):
delay = random.uniform(0.02, 0.15)
await asyncio.sleep(delay)
else:
raise
raise RuntimeError("SQLite write failed after 15 retries")
9.3 本地优先
所有数据存储在本地 ~/.hermes/ 目录,没有云端同步。搬家的时候拷贝目录就行。这种本地优先的设计在隐私敏感场景下是个加分项——你的对话历史和 Agent 记忆不会上传到任何云服务。
从选型上看,SQLite 在这里是一个恰到好处的选择。PostgreSQL 太重了,需要单独的数据库服务;纯文件存储又缺乏查询能力。SQLite + FTS5 提供了全文搜索、事务支持、WAL 并发,同时保持单文件部署的简洁性。
十、性能与部署
10.1 资源占用
- 不跑本地 LLM 的情况下,Agent 进程本身占用不到 500MB
- 官方宣称可以在 $5/月的 VPS 上运行
- 这个数据从架构上看是合理的——SQLite 很轻,Agent 实例的主要开销在对话历史的内存占用上
10.2 模型支持
支持的模型后端:
- Nous Portal
- OpenRouter(200+ 模型)
- NVIDIA NIM
- OpenAI GPT-5.x
- Anthropic Claude Opus 4.6
- Google Gemini 3.1 Pro
- DeepSeek
- Hugging Face(20+ 开放模型)
- Ollama(本地模型)
切换模型只需一条 hermes model 命令,不需要改代码。
10.3 执行环境
支持多种执行环境后端:
- local(本地)
- Docker
- SSH
- Daytona
- Singularity
- Modal
十一、架构评价:做得好的与需要关注的
做得好的地方
- 闭环学习是原生能力:从 PromptBuilder 的
MEMORY_GUIDANCE到 MemoryManager 的双文件架构,再到 SessionDB 的 FTS5 搜索,整套学习闭环在架构层面是贯通的 - 工具自注册 + Toolset 组合:很灵活,新接入平台只需定义差异部分
- 上下文压缩的可插拔架构:给未来的优化留下了空间
- 迭代预算 + 中断机制:体现了对 Agent 实际运行问题的深入理解
- Profile 系统:多实例隔离,实用性很强
需要关注的局限
- 子 Agent 并发上限 3 个:复杂并行场景受限
- 项目迭代节奏很快:不到 3 周发布了 4 个大版本,稳定性需要观察
- Windows 原生不支持:必须通过 WSL2
- 冻结快照的代价:一个会话内对记忆的修改,需要等到下一个会话才能被读取
十二、与其他框架的对比
| 维度 | hermes-agent | LangChain | CrewAI | AutoGen |
|---|---|---|---|---|
| 核心理念 | 自进化 Agent | 工具编排 | 多角色协作 | 多 Agent 对话 |
| 记忆系统 | 三层记忆 + FTS5 | 基础记忆 | 有限记忆 | 对话历史 |
| 学习能力 | 闭环自学习 | 无 | 无 | 有限 |
| 平台支持 | 17+ 平台 | API 调用 | API 调用 | API 调用 |
| 部署复杂度 | 低(单进程) | 中 | 中 | 高 |
| Star 数 | 130K | 100K+ | 30K+ | 40K+ |
十三、总结与展望
hermes-agent 最大的贡献是把 Mitchell Hashimoto 提出的 Harness Engineering 五大组件(指令层、约束层、反馈层、记忆层、编排层)做成了产品级的内建能力。这在 Agent 框架领域是比较少见的。
从架构设计上看,hermes-agent 证明了一件事:AI Agent 可以不只是「工具调用器」,它可以是一个真正的「长期伙伴」。
个人记忆、Skill 自进化、闭环学习——这些能力让 Agent 从「每次对话都失忆」变成了「越用越懂你」。这不是炫技,而是解决了一个真实的用户痛点。
如果你在做 Agent 相关的架构设计,hermes-agent 的闭环学习和上下文管理部分值得细看。开源生态里能做到这个完成度的 Agent 框架,目前确实不多。
未来值得关注的方向:
- Skill 生态的标准化:hermes-agent 已经内置了 Skill 系统,但跨框架的 Skill 复用还没有标准
- 记忆的跨会话一致性:冻结快照解决了前缀缓存问题,但也带来了延迟,如何平衡是下一个课题
- 多 Agent 协作的扩展:当前 3 个子 Agent 的限制可能需要放开
项目地址:https://github.com/NousResearch/hermes-agent
技术栈:Python 3.11+ | SQLite + FTS5 | MIT License