架构总览
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):
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 重启时旧进程没退干净留的后门:
class GatewayRunner(GatewayAuthorizationMixin, GatewayKanbanWatchersMixin, GatewaySlashCommandsMixin):
"""Main gateway controller. Manages the lifecycle of all platform adapters
and routes messages to/from the agent."""核心循环 → 真正干活的地方。run_conversation 在 agent/conversation_loop.py,编排「模型调用 → 工具调用 → 结果回填 → 预算递减」,失败走回退;签名见上。
能力层 → 工具注册。每个 tools/*.py 在模块导入时调 registry.register(tools/registry.py:365),override=True 才允许插件覆盖内置工具:
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 的 tick。tick(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 之间」的职责,不只是接入。