Skip to content

架构总览

源码版本v2026.7.20

Hermes Agent 是 Nous Research 开源(MIT)的桌面端 AI agent,核心定位是「与你一起成长的持久 agent」:一条消息进来,从 Telegram/Discord/Slack 等任意界面接入,经核心循环调用模型+工具完成任务,并把经验沉淀进记忆与技能,供下次复用。

分层视图

各层一句话

  • 界面层:TUI / CLI / Web / ACP(编辑器协议),只是入口,不持有业务。
  • 网关层:gateway/run.py 统一接入,各平台是适配器,会话与投递分离,流式与 hooks 可插拔。
  • 核心循环:run_conversation 编排「模型调用 → 工具调用 → 结果回填 → 预算递减」,失败走回退。
  • 能力层:Tool 是原子函数(tools/registry.py 注册),Skill 是更高阶的可演化流程(被学习闭环改写),Provider/MCP/Plugins 提供模型与外部能力。
  • 调度扩展:Cron 定时驱动、Subagent 隔离派生(零上下文成本)、6 种沙箱后端隔离执行。
  • 学习闭环:把会话经验沉淀成技能与用户模型,形成「越用越好用」的飞轮。

自顶向下:每一层的代表性签名

要理解 Hermes 的分层,最快的办法是看每一层在代码里露出什么「签名」。

界面层 → 核心循环的转发器。所有壳都调 AIAgent.run_conversation,但它只是个转发(run_agent.py:6350):

python
def run_conversation(self, user_message, system_message=None,
                     conversation_history=None, ..., moa_config=None) -> Dict[str, Any]:
    """Forwarder — see ``agent.conversation_loop.run_conversation``."""
    from agent.conversation_loop import run_conversation
    token = set_conversation_context(self._conversation_root_id())
    with scoped_runtime_main({}):
        return run_conversation(self, user_message, ...)

会话 ID 通过 ContextVar 推下去——主循环内的所有 LLM 调用(压缩、视觉、MoA slot、后台审视 fork)都自动带标签。

网关层 → 平台适配器的总管GatewayRunner 用 mixin 拼出授权/看板/斜杠命令能力(gateway/run.py:22392),入口 start_gateway(replace=True) 会先杀旧实例——为 systemd 重启时旧进程没退干净留的后门:

python
class GatewayRunner(GatewayAuthorizationMixin, GatewayKanbanWatchersMixin, GatewaySlashCommandsMixin):
    """Main gateway controller. Manages the lifecycle of all platform adapters
    and routes messages to/from the agent."""

核心循环 → 真正干活的地方run_conversationagent/conversation_loop.py,编排「模型调用 → 工具调用 → 结果回填 → 预算递减」,失败走回退;签名见上。

能力层 → 工具注册。每个 tools/*.py 在模块导入时调 registry.register(tools/registry.py:365),override=True 才允许插件覆盖内置工具:

python
def register(self, name, toolset, schema, handler, check_fn=None,
             requires_env=None, is_async=False, description="", emoji="",
             max_result_size_chars=None, dynamic_schema_overrides=None, override=False):
    """Register a tool.  Called at module-import time by each tool file.
    ``override=True`` is an explicit opt-in for plugins ... Without it,
    registrations that would shadow an existing tool are rejected."""

插件想替换内置工具得显式 override=True 并通过 allow_tool_override 配置——防止插件悄悄把 browser 工具换掉。MCP 工具同名覆盖走单独通道。

调度扩展 → cron 的 ticktick(cron/scheduler.py:3888)是文件锁保护的单次扫描,gateway 每 60 秒从后台线程调一次;adapters 参数让 cron 任务复用 gateway 已建好的平台连接,不用自己重连。

学习闭环 → 学习图汇总build_learning_graph(agent/learning_graph.py:248-328)把学到(非内置)的技能和记忆拼成节点/边。它先调 build_skill_nodes(_skill_roots()) 扫 base 和 profile 两个根,然后过滤 source != "base"created_by == "agent"use_count > 0 的技能当「学到的」——内置技能不进学习图,避免噪声盖过演化信号。

分层动机

为什么这么分层?一句话:让能改的层不污染不能改的层

  • 界面层换壳频繁(TUI/Web/IDE 协议各有各的迭代节奏),不能让换壳逼着改业务;所以壳只挂回调(step_callback/event_callback),不进 run_conversation 内部。
  • 网关层接入新平台是常态(QQbot、企业微信、新 Discord 风格),但核心循环不应感知平台——所以平台逻辑全是适配器,网关只把消息和 agent 调用解耦。
  • 能力层是「原子」,学习闭环是「演化」——原子不能被演化路径污染(否则后台审视一个 bug 把工具改坏了,主循环也跟着崩),所以 learning_mutations 改的是 skills/ 而不是 tools/
  • 调度扩展是横切:Cron 和 Subagent 都是「在主循环之外触发主循环」,共享 run_conversation 但不直接持有 agent 状态。

常见误读

  • 「Hermes 是个 agent 框架」——不是。它是一个具体的桌面 agent 实现,核心循环、工具集、技能目录都是 Hermes 自带的。框架化能力(MCP、Plugin、Provider 抽象)是它为了能扩展而暴露的边界,不是它的本体。
  • 「学习闭环会自动优化模型提示」——不会。它只改 skills/ 下的 SKILL.md 和 ~/.hermes/skills/ 下的记忆,不改系统提示模板。提示模板是仓库里的固定文件,改它要靠发版。
  • 「Subagent 是分布式」——不是。Subagent 在同一进程内 fork agent(共享 runtime),沙箱是执行隔离(代码跑在 Docker/SSH/Modal/Daytona),不是 agent 状态的分布式。
  • 「网关层 = 消息平台接入」——只对了一半。网关还管会话状态(session/)、投递去重(delivery_ledger)、流式分发(stream_*)、斜杠命令、kanban watchers——这些都是「消息平台 + agent 之间」的职责,不只是接入。

阅读顺序建议

  1. 启动与入口 — 一条命令如何变成运行中的 agent
  2. Agent 主循环 — 消息进来后发生什么
  3. 能力层 — 工具与技能的区别
  4. 网关层 — 多平台如何投递
  5. 调度与扩展 — 自动化与隔离
  6. 学习闭环 — 自我进化机制

非官方社区学习站,内容以 MIT 许可的 NousResearch/hermes-agent 源码为依据。