Hermes Agent 深度技术调研:自我进化的开源 AI Agent
基于源码阅读的深度分析 | 仓库: NousResearch/hermes-agent | MIT License | v0.7.0 (2026-04-03)
一、项目概述
Hermes Agent 是 Nous Research 推出的自我进化开源 AI Agent。它不是一个 IDE 绑定的 Copilot,而是一个持久化的自主任务执行框架,核心卖点是闭环学习循环 (Closed Learning Loop) —— Agent 能从经验中自主创建技能、改进技能、持久化知识、搜索历史会话,并跨会话构建用户画像。
关键数据:
- GitHub Stars: ~30,000+ | Contributors: 242+ | Releases: 7 (5周内)
- 核心文件:
run_agent.py(9,423行) |cli.py(8,615行) - 内置工具: 47个 | 内置技能: 70+ | 消息平台: 14+
- 执行后端: 6种 | 支持 LLM Provider: 18+
二、架构设计(源码视角)
2.1 三层架构
┌─────────────────────────────────────────────────┐
│ User Interface Layer │
│ CLI (cli.py) │ Gateway (gateway/run.py) │ ACP │
│ Batch Runner │ Cron Scheduler │ API Server │
├─────────────────────────────────────────────────┤
│ Core Agent Layer │
│ AIAgent (run_agent.py) — 核心编排引擎 │
│ ├── _build_system_prompt() 系统提示词组装 │
│ ├── run_conversation() 主对话循环 │
│ ├── _spawn_background_review() 后台记忆/技能审查│
│ ├── ContextCompressor 上下文压缩 │
│ └── IterationBudget 迭代预算控制 │
├─────────────────────────────────────────────────┤
│ Tool & Execution Layer │
│ ToolRegistry (tools/registry.py) │
│ 47个工具 × 6种执行后端 │
│ (Local/Docker/SSH/Modal/Daytona/Singularity) │
└─────────────────────────────────────────────────┘
2.2 核心循环: run_conversation() (run_agent.py:6882)
这是整个系统的心脏。阅读源码后,我总结出以下执行流程:
用户消息 → 代理编码清理 (surrogate sanitization)
→ 恢复主 runtime (若上轮发生了 fallback)
→ 系统提示词组装 (缓存,仅首次构建)
→ 预飞行上下文压缩 (若历史超阈值)
→ 插件 pre_llm_call hook
→ 外部记忆 provider prefetch
╔══════════════════════════════╗
║ 主循环 (while budget > 0) ║
║ 1. 检查中断请求 ║
║ 2. 消费 iteration budget ║
║ 3. 注入临时上下文到用户消息 ║
║ 4. 应用 Anthropic 缓存控制 ║
║ 5. 清理孤立的 tool_call ║
║ 6. API 调用 (含重试逻辑) ║
║ 7. 处理 tool_calls ║
║ 8. 工具结果追加到消息 ║
║ 9. 检查是否需要压缩上下文 ║
╚══════════════════════════════╝
→ 外部记忆 sync + queue_prefetch
→ 后台记忆/技能审查 (_spawn_background_review)
→ 插件 on_session_end hook
→ 返回结果
源码中几个值得注意的设计细节:
- 迭代预算是线程安全的 (
IterationBudget使用threading.Lock),父 Agent 默认 90 次,子 Agent 默认 50 次 - 系统提示词只构建一次,缓存在
_cached_system_prompt上,后续轮次复用,最大化 Anthropic 前缀缓存命中 - 工具结果超过 100K 字符时自动写入临时文件,只保留 1,500 字符预览 (
_save_oversized_tool_result) - 并行工具执行有严格的安全检查 ——
_should_parallelize_tool_batch()会检查工具是否在_PARALLEL_SAFE_TOOLS白名单中,且路径级工具(read_file/write_file/patch)需要确认路径不重叠
三、四层记忆架构(核心创新)
3.1 架构全景
| 层级 | 存储方式 | 特性 | 角色 |
|---|---|---|---|
| Prompt Memory | MEMORY.md + USER.md | 每次注入系统提示词,上限 3,575 字符 | 语义记忆 (what matters) |
| Session Search | SQLite FTS5 | 按需检索,LLM 摘要后注入 | 情景记忆 (what happened) |
| Skills | ~/.hermes/skills/ 目录 | 渐进式披露,按需加载全文 | 程序记忆 (how to do it) |
| Honcho | 外部辩证引擎 | 被动用户建模,12层身份画像 | 关系记忆 (who you are) |
3.2 Prompt Memory 实现 (tools/memory_tool.py)
这是最核心的记忆层。关键设计决策:
class MemoryStore:
def __init__(self, memory_char_limit=2200, user_char_limit=1375):
# 总上限 3,575 字符 ≈ 约 900 tokens
# 这是一个刻意的设计约束:强制策展而非累积
冻结快照模式 (Frozen Snapshot Pattern):
- 会话开始时加载 MEMORY.md/USER.md,捕获快照注入系统提示词
- 会话中的写入立即持久化到磁盘,但不修改系统提示词
- 原因:保持前缀缓存稳定。这是一个在成本效益和实时性之间的精妙权衡
安全扫描:
_MEMORY_THREAT_PATTERNS = [
(r'ignore\s+(previous|all|above|prior)\s+instructions', "prompt_injection"),
(r'curl\s+[^
]*\$\{?\w*(KEY|TOKEN|SECRET|PASSWORD|CREDENTIAL|API)', "exfil_curl"),
(r'authorized_keys', "ssh_backdoor"),
# ... 更多模式
]
每次写入记忆前都会扫描注入/渗透模式 —— 因为记忆内容会被注入到系统提示词中,这是一个关键的安全边界。
3.3 Session Search (tools/session_search_tool.py)
使用 SQLite FTS5 全文搜索引擎索引历史会话。检索流程:
FTS5 搜索匹配消息 → 按 session 分组 → 取 top 3 unique sessions
→ 加载每个 session 对话 → 截取匹配点附近 ~100K 字符
→ 发送给辅助 LLM 做聚焦摘要 → 返回结构化摘要
这种 检索+摘要 的模式避免了把原始对话直接塞进上下文窗口。
3.4 周期性记忆/技能审查机制
源码中的 nudge 机制是学习循环的关键:
# run_agent.py:1027
self._memory_nudge_interval = 10 # 每10轮用户消息触发一次记忆审查
self._skill_nudge_interval = 10 # 每10次工具迭代触发一次技能审查
当触发条件满足时,_spawn_background_review() 会:
- 创建一个完整的 AIAgent 分支(共享记忆存储)
- 把当前对话历史作为上下文
- 追加审查提示词作为"用户消息"
- 在后台线程中运行(daemon=True),不阻塞用户交互
- 审查 Agent 有权使用 memory 和 skill_manage 工具
- 完成后向用户显示简要摘要(如 "💾 Memory updated · Skill created")
四、技能系统(程序记忆)
4.1 技能结构
~/.hermes/skills/
├── systematic-debugging/
│ └── SKILL.md # YAML frontmatter + Markdown 指令
├── software-development/
│ ├── test-driven-development/
│ │ └── SKILL.md
│ └── subagent-driven-development/
│ └── SKILL.md
└── category/skill-name/
├── SKILL.md
├── references/ # 参考资料
├── templates/ # 模板文件
├── scripts/ # 脚本
└── assets/ # 资源
4.2 渐进式披露 (Progressive Disclosure)
技能不是一次性全部加载的:
# agent/prompt_builder.py - build_skills_system_prompt()
# 默认只加载技能名称 + 描述摘要(几十字节/技能)
# 只有 Agent 判断相关时才加载完整内容
# 这保持了 token 使用量与技能数量无关(70+技能不膨胀)
4.3 技能自改进
技能不是只读的,Agent 可以通过 6 种操作修改技能:
# tools/skill_manager_tool.py
# Actions: create, edit, patch, delete, write_file, remove_file
# 默认使用 patch(精准替换)而非 edit(全量重写)
触发条件(从 prompt_builder.py 的 SKILLS_GUIDANCE 提取):
- 5次以上工具调用的复杂任务完成后
- 修复了一个棘手的错误
- 发现了非显而易见的工作流
安全扫描: Agent 创建的技能会经过与社区技能相同的安全扫描(skills_guard.py),检测渗透、注入、破坏性命令等模式。
五、工具系统
5.1 注册表模式 (tools/registry.py)
class ToolRegistry:
"""单例注册表,收集来自工具文件的 schema + handler"""
def register(self, name, toolset, schema, handler, check_fn=None, ...):
"""在模块导入时由每个工具文件调用"""
def get_definitions(self, tool_names, quiet=False):
"""返回 OpenAI 格式的工具 schema(只包含 check_fn 通过的工具)"""
def dispatch(self, name, args, **kwargs):
"""按名称执行工具 handler(自动桥接 async)"""
这种自注册模式解耦了工具实现和编排逻辑。每个工具文件在导入时调用 registry.register(),无需修改中央文件。
5.2 47 个内置工具 (20 个工具集)
| 工具集 | 代表工具 | 说明 |
|---|---|---|
| terminal | terminal | 6种后端执行命令 |
| browser | browser_* (11个) | Playwright + Camofox 反检测浏览器 |
| web | web_search, web_extract | 搜索和内容提取 |
| file | read_file, write_file, patch, search_files | 文件操作 |
| code_execution | execute_code | 沙盒化代码执行 |
| vision | vision_analyze | 图像分析 |
| image_generation | image_generate | 图像生成 (fal.ai) |
| tts | text_to_speech | 语音合成 (edge-tts / ElevenLabs) |
| delegation | delegate_task | 子 Agent 派遣 |
| memory | memory | 持久化记忆管理 |
| skills | skills_list, skill_view, skill_manage | 技能系统 |
| session_search | session_search | 历史会话搜索 |
| todo | todo | 任务规划 |
| cron | cron_* | 定时任务 |
| mcp | 动态工具 | MCP 协议扩展 |
5.3 危险命令审批系统 (tools/approval.py)
DANGEROUS_PATTERNS = [
(r'\brm\s+-[^\s]*r', "recursive delete"),
(r'\bchmod\s+(-[^\s]*\s+)*(777|666|o\+[rwx]*w|a\+[rwx]*w)\b', "world-writable permissions"),
(r'\bDROP\s+(TABLE|DATABASE)\b', "SQL DROP"),
(r':\(\)\s*\{\s*:\s*\|\s*:\s*&\s*\}\s*;\s*:', "fork bomb"),
(r'\b(curl|wget)\b.*\|\s*(ba)?sh\b', "pipe remote content to shell"),
# ... 30+ 个模式
]
关键特性:
- 命令归一化:先 strip ANSI 转义、null 字节、Unicode 全角字符,防止绕过
- 三级审批:once(本次)/ session(会话内)/ always(永久白名单写入 config.yaml)
- Gateway 异步审批:网关模式下通过
threading.Event队列实现跨线程阻塞审批 - Smart Approval:可选使用辅助 LLM 自动评估低风险命令
六、执行后端
6.1 策略模式 (tools/environments/base.py)
class BaseEnvironment(ABC):
"""所有 Hermes 执行后端的通用接口"""
@abstractmethod
def execute(self, command, cwd="", *, timeout=None, stdin_data=None) -> dict:
"""执行命令,返回 {"output": str, "returncode": int}"""
@abstractmethod
def cleanup(self):
"""释放后端资源(容器、实例、连接)"""
6 种后端通过配置切换,无需代码变更:
| 后端 | 文件 | 适用场景 |
|---|---|---|
| Local | environments/local.py | 直接宿主机执行(默认,最快) |
| Docker | environments/docker.py | 隔离容器(只读root,降权) |
| SSH | environments/ssh.py | 远程服务器 |
| Modal | environments/modal.py | 无服务器云沙箱 |
| Daytona | environments/daytona.py | 托管工作空间环境 |
| Singularity | environments/singularity.py | HPC/科研环境 |
6.2 Docker 安全默认值
Docker 后端的安全策略在源码中清晰可见:只读根文件系统、丢弃特权、命名空间隔离。
七、消息网关 (14+ 平台)
7.1 架构
# gateway/run.py - GatewayRunner
# 单进程管理多平台适配器
# 每个平台独立的 adapter 文件
支持平台(gateway/platforms/ 目录):
| 平台 | 文件 | 特性 |
|---|---|---|
| Telegram | telegram.py | 语音转写、文件上传、贴纸缓存 |
| Discord | discord.py | 语音频道、线程 |
| Slack | slack.py | Bolt 框架 |
whatsapp.py | Business API | |
| Signal | signal.py | 端到端加密 |
| Matrix | matrix.py | E2E 加密 (nio) |
email.py | IMAP/SMTP | |
| SMS | sms.py | 短信 |
| DingTalk | dingtalk.py | 钉钉 |
| Feishu | feishu.py | 飞书/Lark |
| WeCom | wecom.py | 企业微信 |
| Home Assistant | homeassistant.py | 智能家居 |
| Mattermost | mattermost.py | 开源 Slack 替代 |
| Webhook | webhook.py | 通用 webhook |
所有平台共享同一个对话上下文,实现跨平台对话连续性。
八、Prompt 缓存优化
8.1 Anthropic 前缀缓存 (agent/prompt_caching.py)
def apply_anthropic_cache_control(api_messages, cache_ttl="5m", native_anthropic=False):
"""system_and_3 策略:系统提示词 + 最后3条非系统消息"""
# Anthropic 最多允许 4 个 cache_control 断点
# 1. 系统提示词(跨所有轮次稳定)
# 2-4. 最后 3 条非系统消息(滚动窗口)
为什么系统提示词永不中途修改?
这是一条硬性架构规则。源码中多处体现:
_cached_system_prompt只构建一次,后续复用- 继续会话时从 SessionDB 加载存储的系统提示词(而非重建)
- 外部记忆上下文注入到用户消息而非系统提示词
- 插件上下文同样注入到用户消息
效果:输入 token 成本降低约 75%。
九、上下文压缩 (agent/context_compressor.py)
class ContextCompressor:
"""算法:
1. 裁剪旧工具结果(便宜,不需要 LLM)
2. 保护头部消息(系统提示词 + 首次交互)
3. 按 token 预算保护尾部消息(最近 ~20K tokens)
4. 用结构化 LLM 提示词摘要中间轮次
5. 后续压缩迭代式更新之前的摘要
"""
关键参数:
threshold_percent = 0.50— 上下文使用 50% 时触发_SUMMARY_RATIO = 0.20— 摘要占压缩内容的 20%_SUMMARY_TOKENS_CEILING = 12_000— 摘要最大 token 数_PRUNED_TOOL_PLACEHOLDER— 旧工具结果替换为占位符
摘要模板是结构化的:目标、进展、决策、文件、下一步。这比自由格式摘要保留了更多信息。
十、子 Agent 委派 (tools/delegate_tool.py)
DELEGATE_BLOCKED_TOOLS = frozenset([
"delegate_task", # 禁止递归委派
"clarify", # 禁止用户交互
"memory", # 禁止写共享 MEMORY.md
"send_message", # 禁止跨平台副作用
"execute_code", # 子 Agent 应逐步推理
])
MAX_CONCURRENT_CHILDREN = 3
MAX_DEPTH = 2 # parent(0) -> child(1) -> grandchild rejected(2)
DEFAULT_MAX_ITERATIONS = 50
子 Agent 设计要点:
- 独立的上下文(不继承父 Agent 历史)
- 受限工具集(可配置,但始终移除被封锁工具)
- 独立的 IterationBudget
- 父 Agent 只看到委派调用和摘要结果,不看中间步骤
十一、安全体系
11.1 多层防御
| 层级 | 机制 | 实现 |
|---|---|---|
| 输入 | Prompt 注入扫描 | prompt_builder.py — 10个威胁模式 + 不可见字符检测 |
| 记忆 | 记忆内容扫描 | memory_tool.py — 注入/渗透模式检测 |
| 技能 | 安装前安全扫描 | skills_guard.py — 信任级别 + 威胁模式 |
| 命令 | 危险命令审批 | approval.py — 30+ 模式 + 命令归一化 |
| 工具 | 输出大小限制 | 100K 字符阈值,超限写文件 |
| 执行 | 容器隔离 | Docker 只读根、降权 |
| 输出 | Secret 渗透检测 | 浏览器 URL 和 LLM 响应扫描 |
11.2 不可见字符检测
_CONTEXT_INVISIBLE_CHARS = {
'\u200b', # Zero-width space
'\u200c', # Zero-width non-joiner
'\u200d', # Zero-width joiner
'\u2060', # Word joiner
'\ufeff', # Zero-width no-break space (BOM)
'\u202a'-'\u202e', # Bidi override characters
}
十二、Provider 支持与凭据池
12.1 18+ LLM Provider
Nous Portal, OpenRouter (200+ models), Anthropic, OpenAI, z.ai/GLM, Kimi/Moonshot, MiniMax, Alibaba/DashScope, DeepSeek, Hugging Face, 以及自定义 OpenAI 兼容端点。
12.2 凭据池 (agent/credential_pool.py)
STRATEGY_FILL_FIRST = "fill_first"
STRATEGY_ROUND_ROBIN = "round_robin"
STRATEGY_RANDOM = "random"
STRATEGY_LEAST_USED = "least_used" # 默认
EXHAUSTED_TTL_429_SECONDS = 60 * 60 # 429 限速:冷却1小时
EXHAUSTED_TTL_DEFAULT_SECONDS = 24 * 60 * 60 # 其他故障:冷却24小时
同一 provider 可配置多个 API key,线程安全的 least_used 策略分发负载,401 故障自动切换到下一个凭据。
十三、技术栈
核心语言: Python 3.11+ (93.4%)
包管理: uv (Astral)
数据库: SQLite + FTS5
CLI UI: Rich + prompt_toolkit + KawaiiSpinner
LLM SDK: openai + anthropic
Web: httpx + requests + exa-py + firecrawl-py
浏览器: Playwright + Camoufox
TTS: edge-tts (免费) + ElevenLabs (付费)
STT: faster-whisper
配置: YAML (pyyaml)
MCP: mcp >= 1.2.0
测试: pytest (3,000+ test cases)
构建: Nix Flake (可复现构建)
十四、与竞品对比
| 维度 | Hermes Agent | OpenClaw (OpenHands) | Claude Code |
|---|---|---|---|
| 定位 | 自进化学习 Agent | 广生态集成 | 开发者编码助手 |
| 记忆 | 4层架构 (prompt/episodic/procedural/dialectic) | 基础上下文 | 文件笔记 |
| 技能系统 | agentskills.io 可移植标准,自创建+自改进 | 插件 | 仅工具调用 |
| 消息平台 | 14平台,单网关进程 | 有限 | Telegram/Discord |
| 执行后端 | 6种 (local/Docker/SSH/Modal/Daytona/Singularity) | 主要本地 | 本地 |
| 成本 | 仅 LLM API 费用;可跑在 $5 VPS 上 | $59/月(云) 或自托管 | 订阅制 |
| 安全 | 命令审批 + 注入扫描 + Docker 沙盒 + Secret 检测 | 标准 | 权限提示 |
| 开源 | MIT | 开源 | 非开源 |
十五、核心设计洞见
15.1 3,575 字符的 Prompt Memory 上限
这是最有趣的设计决策之一。memory_char_limit=2200 + user_char_limit=1375 = 3575 字符,约 900 tokens。
为什么这么小? 这强制 Agent 策展而非累积。如果无限增长,记忆会变成垃圾场。3,575 字符的约束迫使 Agent 只保留真正有价值的信息。
15.2 冻结快照 vs 实时更新
系统提示词中的记忆是会话开始时的快照,中途不变。工具调用可以修改磁盘上的记忆文件,但当前系统提示词保持不变。
权衡:牺牲了记忆的即时性,换取了 75% 的缓存成本节省。对于大多数场景,这是一个正确的权衡。
15.3 后台审查而非内联审查
记忆和技能的审查在后台线程中运行(_spawn_background_review),使用独立的 AIAgent 实例。这意味着:
- 用户不需要等待审查完成
- 审查不会打断当前任务的思路
- 审查结果在下次会话才生效(因为冻结快照)
15.4 agentskills.io 标准
技能使用 YAML frontmatter + Markdown 格式,可在兼容的 Agent 框架间迁移。这是一个生态系统赌注 —— 如果被广泛采用,将成为 Agent 领域的"npm"。
15.5 Prompt Injection 的全链路防御
从上下文文件加载、记忆写入、技能安装到命令执行,每一层都有独立的注入检测。这不是事后加的安全补丁,而是从架构层面贯穿的设计理念。
十六、总结
Hermes Agent 的核心创新不是单个特性,而是一个完整的自我进化闭环:
任务执行 → 经验积累 → 记忆策展 → 技能创建
↑ ↓
← ← ← ← 技能改进 ← 技能检索 ← ← ←
这个闭环的每个环节都有精心的工程实现:
- 记忆有容量约束和安全扫描
- 技能有渐进披露和自改进
- 审查在后台运行不阻塞用户
- 缓存策略贯穿整个架构
在 5 周内发布 7 个版本、积累 30K Stars 和 242 名贡献者,说明这个项目击中了开源 AI Agent 领域的一个真实需求:一个不绑定特定 IDE、不锁定特定 LLM、能从使用中学习的通用 Agent 框架。
基于 v0.7.0 源码 (commit 2026-04-03) 深度阅读分析 | 分析日期: 2026-04-07