Nous Research, Teknium | 2026-04 | https://github.com/NousResearch/hermes-agent Category: code | Tags: agent, self-improving, skills, memory, multi-platform, MCP, tool-calling, RL-training Read: 2026-04-16
开源的自我进化 AI Agent 框架,具备闭环学习能力(技能自动创建与改进、持久记忆、用户建模),支持 20+ 平台网关和 6 种终端后端,从 5 美元 VPS 到 GPU 集群均可运行。
Hermes Agent 是 Nous Research 构建的开源 AI Agent,核心特点是「自我改进闭环」:Agent 在完成复杂任务后自动创建技能(Skill),后续使用中持续改进技能,并通过持久记忆和用户画像实现跨会话的个性化。
架构上,项目以 run_agent.py(10K 行)中的 AIAgent.run_conversation 为核心循环,实现了多 Provider 适配(OpenAI/Anthropic/OpenRouter/200+ 模型)、流式响应、工具调用(40+ 内置工具)、上下文压缩、会话持久化(JSONL + SQLite FTS5)、子代理委托等。Gateway 系统支持 Telegram/Discord/Slack/WhatsApp/Signal/飞书/钉钉等 20+ 平台,统一通过 BasePlatformAdapter 抽象层适配。
项目还包含面向研究的批量轨迹生成(batch_runner.py)、轨迹压缩(trajectory_compressor.py)和 Atropos RL 环境集成,用于训练下一代工具调用模型。v0.8.0 版本已实现 MCP 双向集成(既是 MCP 客户端也是 MCP 服务端)、ACP 适配器、cron 调度等企业级功能。
2025-2026 年,AI Agent 从概念验证进入实用化竞赛。Claude Code、Cursor Agent、Devin、OpenHands 等产品密集发布。但多数 Agent 存在两个根本缺陷:(1) 无记忆——每次对话从零开始,不会从过去的成功/失败中学习;(2) 平台绑定——只能在特定 IDE 或终端中使用。Hermes Agent 试图在开源领域建立一个「持久存在、持续进化」的 Agent 范式。
Claude Code 等商业产品虽然强大,但闭源且绑定特定 Provider。OpenHands 等开源方案侧重沙箱执行,缺乏学习闭环。Cursor 有 Skills 但不开源。市场上没有一个开源的、自带学习闭环的、平台无关的 Agent 框架。
为何不可每次重新生成技能? 因为技能的价值在于累积经验——一个经过 10 次使用改进的技能比新生成的要好得多,重新生成等于丢弃了所有实践反馈。
为何不可把记忆放在上下文中? 因为上下文窗口有限(128K-1M tokens),而用户画像和项目知识会持续增长。必须持久化到文件/DB,按需注入。
为何不可统一所有平台的 API? 因为 Telegram 支持 Markdown、Discord 有 embed、Slack 用 Block Kit、WhatsApp 只有纯文本——渲染差异太大。解决方案是 BasePlatformAdapter 抽象 + PLATFORM_HINTS 让 Agent 根据平台调整输出格式。
核心 insight 是:Agent 的价值不在单次执行能力,而在于跨会话的知识积累。Hermes 把 Agent 从「工具」变成「助手」——它认识你、记得你的偏好、能从错误中学习、随时间变得更好。这就像一个新员工 vs 一个跟了你三年的助理的区别。
run_agent.py 中 AIAgent.run_conversation 的双轨消息设计是整个系统能稳定工作的关键:messages(持久化真相源)与 api_messages(每轮构造的请求视图)分离。这使得 ephemeral 插件注入(memory prefetch、context engine)不会污染持久化历史,同时 Anthropic prefix cache 的前缀可以保持稳定。没有这个设计,多轮对话中的缓存命中率会骤降,推理成本翻倍。
AIAgent.run_conversation 采用「双轨消息」设计——messages(持久化历史)与 api_messages(每轮构造的请求视图)分离,实现 prefix cache 稳定性与 ephemeral 注入的解耦registry.dispatch(40+ 普通工具)和 _invoke_tool(todo/memory/session_search/delegate 等需要 Agent 状态的工具),避免了循环依赖parent_session_id 链)BasePlatformAdapter 抽象支持 20+ 平台,所有平台共享统一的授权/命令/agent 管道classify_api_error 统一分类→credential pool 轮换→fallback provider→jittered backoff→stale stream 检测→可中断重试run_agent.py 单文件 10K+ 行,传输层、业务编排、边界情况恢复耦合在一起,可维护性是挑战
hermes-agent/
├── run_agent.py # 核心 Agent 循环(10K 行,AIAgent 类)
├── cli.py # 交互式 TUI(prompt_toolkit,9.7K 行)
├── model_tools.py # 工具系统门面:发现、定义、分发
├── toolsets.py # 工具集配置与解析
├── hermes_state.py # SQLite (WAL) 会话/消息持久化 + FTS5
├── hermes_constants.py # 全局常量
├── hermes_logging.py # 分组件日志
├── hermes_time.py # 时间工具
├── agent/ # Agent 内部模块
│ ├── prompt_builder.py # 系统提示组装
│ ├── context_compressor.py # 上下文压缩引擎
│ ├── anthropic_adapter.py # Anthropic API 适配
│ ├── auxiliary_client.py # 辅助 LLM 调用
│ ├── memory_manager.py # 记忆插件编排
│ ├── error_classifier.py # API 错误分类与恢复策略
│ ├── skill_utils.py # 技能索引与加载
│ └── ... # credential_pool, rate_limit, display 等
├── tools/ # 40+ 工具实现
│ ├── registry.py # 工具注册中心
│ ├── terminal_tool.py # 终端执行
│ ├── file_tools.py # 文件读写搜索
│ ├── browser_tool.py # 浏览器自动化
│ ├── mcp_tool.py # MCP 客户端
│ └── ...
├── gateway/ # 多平台消息网关
│ ├── run.py # GatewayRunner 主入口(8.6K 行)
│ ├── config.py # 平台配置与枚举
│ └── platforms/ # 20+ 平台适配器
│ ├── base.py # BasePlatformAdapter 抽象
│ ├── telegram.py
│ ├── discord.py
│ ├── feishu.py
│ └── ...
├── hermes_cli/ # CLI 子命令
│ ├── main.py # `hermes` 入口
│ ├── commands.py # 斜杠命令注册表
│ └── ...
├── skills/ # 内置技能(YAML+Markdown)
├── plugins/ # 插件(记忆 provider 等)
├── cron/ # 定时任务调度
├── mcp_serve.py # MCP 服务端(暴露 Hermes 给 Cursor/Claude Code)
├── batch_runner.py # 批量轨迹生成
├── trajectory_compressor.py # 离线轨迹压缩
├── tests/ # 测试套件
└── website/ # 文档站
| Module | Directory | LOC | Responsibility |
|---|---|---|---|
| Agent Loop | run_agent.py | ~10,500 | 对话编排、工具执行、流式、重试、压缩、持久化 |
| CLI TUI | cli.py | ~9,800 | prompt_toolkit 交互界面、快捷键、slash 命令 |
| Gateway | gateway/ | ~22,000 | 20+ 平台消息网关、授权、会话管理 |
| Tools | tools/ | ~12,000 | 40+ 内置工具注册与实现 |
| Agent Internals | agent/ | ~8,000 | 提示词、压缩、错误分类、记忆、技能 |
| State | hermes_state.py | ~1,200 | SQLite WAL + FTS5 会话持久化 |
| Model Tools | model_tools.py | ~700 | 工具发现、定义生成、分发 |
| Batch/RL | batch_runner.py + trajectory_compressor.py | ~3,500 | 轨迹生成与压缩 |
┌─────────────────────────────────────────────────────────────────┐
│ User Interfaces │
│ ┌──────────┐ ┌───────────┐ ┌──────────┐ ┌───────────────┐ │
│ │ CLI TUI │ │ Telegram │ │ Discord │ │ Slack/飞书/...│ │
│ │ (cli.py) │ │ (gateway) │ │ (gateway)│ │ (gateway) │ │
│ └────┬─────┘ └─────┬─────┘ └────┬─────┘ └──────┬────────┘ │
│ │ │ │ │ │
│ └───────────────┴──────┬──────┴───────────────┘ │
│ │ │
│ ┌─────────▼──────────┐ │
│ │ AIAgent Loop │ │
│ │ (run_agent.py) │ │
│ │ │ │
│ │ ┌───────────────┐ │ │
│ │ │ messages │ │ ← 持久化历史 │
│ │ │ (真相源) │ │ │
│ │ └───────┬───────┘ │ │
│ │ │ │ │
│ │ ┌───────▼───────┐ │ │
│ │ │ api_messages │ │ ← 每轮构造(ephemeral) │
│ │ │ (请求视图) │ │ │
│ │ └───────┬───────┘ │ │
│ │ │ │ │
│ │ ┌────▼────┐ │ │
│ │ │ LLM API │ │ │
│ │ │ (stream)│ │ │
│ │ └────┬────┘ │ │
│ │ │ │ │
│ │ tool_calls? │ │
│ │ ┌────▼────┐ │ │
│ │ │ Execute │ │ │
│ │ │ Tools │ │ │
│ │ └────┬────┘ │ │
│ │ │ │ │
│ │ continue/break │ │
│ └────────────────────┘ │
│ │ │
│ ┌───────────────┼───────────────┐ │
│ │ │ │ │
│ ┌────────▼──────┐ ┌─────▼──────┐ ┌──────▼──────┐ │
│ │ Tool Registry │ │ Memory │ │ Skills │ │
│ │ (40+ tools) │ │ (MD+Honcho)│ │ (YAML+MD) │ │
│ └───────────────┘ └────────────┘ └─────────────┘ │
│ │ │
│ ┌─────────▼──────────┐ │
│ │ SessionDB │ │
│ │ (SQLite WAL+FTS5) │ │
│ └────────────────────┘ │
└─────────────────────────────────────────────────────────────────┘
messages 用于持久化,api_messages 每轮构造用于请求。好处:prefix cache 稳定、ephemeral 注入不污染历史。代价:复杂度显著增加(两套消息需要同步字段映射)。registry.dispatch。好处:避免向 registry 传递 Agent 内部状态(TodoStore, SessionDB 等)。代价:工具执行逻辑有两份实现,增加了维护负担。run_conversation 内用 api_mode 分支处理。好处:紧密控制每种 API 的边界情况。代价:run_agent.py 膨胀到 10K 行。~/.hermes/skills///SKILL.md 。好处:人类可读可编辑,git 友好。代价:多实例需要共享文件系统。AIAgent,共享完全相同的循环。好处:一致的行为。代价:网关需要处理 Agent 的非线程安全假设。
Entry: hermes (hermes_cli/main.py:main)
Input: CLI subcommands (chat, gateway, model, tools, config, doctor, ...)
Output: Interactive TUI or subcommand result
Side effects: Reads/writes ~/.hermes/ config and state
Entry: hermes-agent (run_agent.py:main)
Input: --model, --toolset, --query, conversation_history
Output: AIAgent.run_conversation result dict
Side effects: Tool execution (file/terminal/web), session persistence
Entry: hermes-acp (acp_adapter/entry.py:main)
Input: ACP protocol messages
Output: ACP responses
Side effects: Delegates to AIAgent
Entry: hermes mcp serve (mcp_serve.py)
Input: MCP stdio protocol (from Cursor/Claude Code)
Output: conversations, messages, events
Side effects: Reads session state, can send messages
| Structure | File | Purpose | Lifetime | Thread Safety |
|---|---|---|---|---|
AIAgent | run_agent.py | 对话编排器,持有所有状态 | Per conversation (CLI) / Per message (Gateway) | 非线程安全,单线程使用 |
messages: List[Dict] | run_agent.py | 持久化对话历史(user/assistant/tool) | Per conversation | 主线程独占 |
api_messages: List[Dict] | run_agent.py | 每轮构造的 API 请求视图 | Per API call | 局部变量 |
IterationBudget | run_agent.py | 线程安全迭代预算计数器 | Per conversation | threading.Lock |
SessionDB | hermes_state.py | SQLite WAL 会话/消息数据库 | Singleton | BEGIN IMMEDIATE + 随机退避 |
MemoryStore | tools/memory_tool.py | MEMORY.md + USER.md 读写 | Per AIAgent | 主线程独占 |
ContextCompressor | agent/context_compressor.py | 上下文压缩引擎 | Per AIAgent | 主线程独占 |
TodoStore | tools/todo_tool.py | 任务列表管理 | Per conversation | 主线程独占 |
GatewayRunner | gateway/run.py | 网关主进程,持有所有平台 adapter | Singleton | 各平台在独立线程/协程 |
BasePlatformAdapter | gateway/platforms/base.py | 平台抽象基类 | Per platform | 平台内单线程 |
[User Input]
→ cli.py: _on_enter() / gateway: _handle_message()
→ AIAgent.run_conversation(user_message, conversation_history)
→ _build_system_prompt() [if not cached] ~10ms
→ _compress_context() [if tokens > threshold] ~2-5s (LLM call)
→ while iteration_budget.remaining > 0:
→ _build_api_kwargs(api_messages, tools) ~1ms
→ _interruptible_streaming_api_call() ~1-30s (LLM)
→ stream deltas → stream_callback (TUI/platform)
→ if tool_calls:
→ _execute_tool_calls() ~0.1-60s
→ handle_function_call() / _invoke_tool()
→ append tool results to messages
→ continue
→ else:
→ final_response = content
→ break
→ _persist_session() → JSONL + SQLite ~10ms
→ synthesis (memory sync, skill nudge) ~100ms
← result dict with final_response
← Display to user / Send to platform
run_conversation)在单线程中执行,包括 LLM API 调用和工具执行。_should_parallelize_tool_batch 判断后可用 ThreadPoolExecutor(最多 8 worker)并行执行工具,前提是工具路径不冲突且都在安全集合中。_handle_message 在各平台的回调中执行。BEGIN IMMEDIATE 防止并发写冲突,有随机退避重试。AIAgent 本身不是线程安全的。Gateway 中如果同一用户在短时间内发送多条消息,需要靠 _active_conversations 锁来排队。| Issue | Location | Severity | Impact |
|---|---|---|---|
| 巨型单文件 | run_agent.py (10K 行) | High | 传输层/编排/恢复耦合,难以独立测试和修改 |
| 工具执行双路径 | _execute_tool_calls_sequential vs _invoke_tool | Medium | 逻辑重复,新工具需要在两处都加 |
| Dict 代替 dataclass | messages, result, api_kwargs | Medium | 无类型检查,字段名拼写错误不会报错 |
| 字符串拼接 system prompt | _build_system_prompt | Low | 可读性差,但功能正确 |
| 魔术数字 | 各种阈值(85%, 50%, 8 workers, 3 retries) | Low | 散落各处,缺乏集中配置 |
| Feature | Hermes Agent | Claude Code | Cursor Agent | OpenHands |
|---|---|---|---|---|
| 开源 | ✅ MIT | ❌ | ❌ | ✅ MIT |
| 自我改进技能 | ✅ 创建+patch | ❌ | ✅ Skills | ❌ |
| 持久记忆 | ✅ MD+Honcho | ❌ | ❌ | ❌ |
| 多平台 | ✅ 20+ | ❌ CLI only | ❌ IDE only | ❌ Web only |
| 模型无关 | ✅ 200+ | ❌ Claude only | ✅ 多模型 | ✅ 多模型 |
| MCP 集成 | ✅ 双向 | ✅ 客户端 | ✅ 客户端 | ❌ |
| RL 训练管线 | ✅ Atropos | ❌ | ❌ | ❌ |
| 上下文压缩 | ✅ 两层 | ✅ 内置 | ✅ 内置 | ✅ 基础 |
| 子代理委托 | ✅ | ✅ | ✅ | ❌ |
| 定时任务 | ✅ Cron | ❌ | ❌ | ❌ |
Should you use it?
Top 3 architectural improvements:
run_agent.py——用状态机或 strategy pattern 将 API 模式(OpenAI/Anthropic/Codex)、工具执行、错误恢复拆成独立模块