乐于分享
好东西不私藏

Hermes Agent 源码解析(八):插件系统 —— 三层插拔架构

Hermes Agent 源码解析(八):插件系统 —— 三层插拔架构

Hermes Agent 源码解析(八):插件系统 —— 三层插拔架构

我刚开始读 Hermes 源码时,以为插件就是一套东西——一个 PluginManager 全管了。翻完代码才发现,它其实是三个各自独立、互不感知的子系统。

三层总览

  1. 1. 通用插件(General Plugins)生命周期钩子 + 自定义工具 + CLI 子命令入口:hermes_cli/plugins.py
  2. 2. Model Provider 插件推理后端适配器(OpenRouter / Anthropic / Gemini…)入口:plugins/model-providers/ + providers/
  3. 3. Memory Provider 插件记忆后端适配器(Honcho / Mem0 / Supermemory…)入口:plugins/memory/

第一层:通用插件(hermes_cli/plugins.py

PluginManager 从四个来源发现插件:

  1. 1. Bundled — <repo>/plugins/<name>/(随仓库发布)
  2. 2. User — ~/.hermes/plugins/<name>/(用户安装)
  3. 3. Project — ./.hermes/plugins/<name>/(项目级,需 HERMES_ENABLE_PROJECT_PLUGINS 开启)
  4. 4. Pip — 暴露 hermes_agent.plugins entry-point 的 pip 包

后三个覆盖前面的同名插件,允许用户替换内置插件。

插件清单与注册

每个插件目录必须包含:

插件目录结构

register(ctx) 中可以做的事:

defregister(ctx):    ctx.register_hook("pre_tool_call", my_pre_tool_hook)    ctx.register_hook("post_tool_call", my_post_tool_hook)    ctx.register_hook("on_session_start", my_start_hook)    ctx.register_tool(name="my_tool", toolset="custom", ...)

支持的钩子

  • • pre_tool_call — 工具执行前
  • • post_tool_call — 工具执行后
  • • pre_llm_call — LLM 调用前
  • • post_llm_call — LLM 调用后
  • • on_session_start — 会话开始
  • • on_session_end — 会话结束

钩子的调用位置:

  • • Pre/Post tool → model_tools.py
  • • Lifecycle → run_agent.py

发现时机坑

discover_plugins() 只在 model_tools.py 被 import 时副作用触发。如果代码路径在没 import model_tools.py 前就读取插件状态,需要显式调用 discover_plugins()(它是幂等的)。

第二层:Model Provider 插件

每个推理后端(OpenRouter、Anthropic、Gemini、Bedrock、DeepSeek……)是一个独立的插件目录:

plugins/model-providers/  ├── openrouter/  │   ├── __init__.py    ← register_provider(ProviderProfile(...))  │   └── ...  ├── anthropic/  ├── gmi/  ├── deepseek/  └── ... 10+

独立发现系统

这不是通过通用 PluginManager 加载的。providers/__init__.py._discover_providers() 是独立的、惰性的系统 —— 首次调用 get_provider_profile() 或 list_providers() 时才扫描。

扫描顺序:

  1. 1. Bundled: <repo>/plugins/model-providers/<name>/
  2. 2. User: $HERMES_HOME/plugins/model-providers/<name>/
  3. 3. Legacy: <repo>/providers/<name>.py(向后兼容)

用户插件同名覆盖内置 —— 第三方可替换任意内置提供商配置。

ProviderProfile

每个提供商通过 register_provider(ProviderProfile(...)) 注册自己的配置:

ProviderProfile(    name="openrouter",    display_name="OpenRouter",    models=["claude-opus-4""gpt-4o", ...],    requires_env=["OPENROUTER_API_KEY"],)

第三层:Memory Provider 插件

plugins/memory/ 目录下的插件实现 MemoryProvider ABC(agent/memory_provider.py):

classMemoryProvider:asyncdefsync_turn(self, turn_messages: list): ...asyncdefprefetch(self, query: str) -> str: ...asyncdefshutdown(self): ...asyncdefpost_setup(self, hermes_home, config): ...

内置提供者:Honcho、Mem0、Supermemory、ByteRover、Hindsight、Holographic、OpenViking、RetainDB。

政策(2026 年 5 月):不再接受新的内置 memory provider PR。新后端应作为独立插件仓库发布。

核心规则

插件不得修改核心文件。 如果插件需要框架未暴露的能力,扩展通用插件表面(新钩子、新 ctx 方法),永远不要将插件专用逻辑硬编码到核心。

这条规则在 PR #5295 后确立,当时移除了 main.py 中 95 行硬编码的 honcho argparse 代码。



欢迎一起讨论! 三层插件系统有三套发现机制,你觉得应该统一吗?你是怎么设计插件系统的?

📢 关注我,获取最新开源更新日志和开发者工具动态。如果觉得这篇文章有用,欢迎转发给身边的开发者朋友。

#源码解析 #HermesAgent #插件架构 #开源项目

下一篇讲 Skills —— Agent 的自我进化闭环。