核心命题:插件系统的本质问题不是技术,而是政治——谁对代码负责?谁承担维护成本?Hermes 在 2026 年 5 月关闭内置 memory provider 入库的决定,是一个把架构原则转化为明确政策的案例。


插件系统的起点:为什么需要三个维度

Hermes 的插件系统不是一个统一的注册表,而是三个独立的子系统:

  1. 通用插件(hermes_cli/plugins.py + ~/.hermes/plugins/<name>/):注册工具、生命周期钩子、CLI 子命令
  2. 记忆提供者插件(plugins/memory/<name>/):实现 MemoryProvider ABC,提供跨会话记忆
  3. 模型提供者插件(plugins/model-providers/<name>/):注册 ProviderProfile,提供推理后端

为什么是三个系统而不是一个?答案和技术无关,和耦合程度与变更频率有关。

考虑一个场景:你提交了一个 PR,给通用插件系统添加了一个新的 post_llm_call 钩子。这个改动需要修改 hermes_cli/plugins.py、model_tools.py(插件调用点)、以及插件 API 的版本号。如果工具插件和记忆提供者在同一个注册表里,这次钩子 API 的版本迁移就需要通知并更新所有记忆提供者——即使记忆提供者的代码一字未动,它也需要声明"我兼容新版本的钩子 API"。

反过来,记忆提供者经常需要修改它们与 MemoryManager 的交互协议(新的生命周期钩子、更丰富的上下文参数)。如果这些修改在同一个注册表里进行,每次协议变更都需要通知所有工具插件——即使工具插件和记忆完全无关。

三个独立系统让每个维度可以独立演进。MemoryProvider ABC 可以在不影响通用插件 API 的情况下添加新钩子(on_pre_compress、on_delegation 是后来添加的)。模型提供者可以在不知道通用插件系统存在的情况下注册新的推理后端。

这个分离的传递性代价是什么?发现逻辑被复制了三份:通用插件用目录扫描 + pip entry points,记忆提供者用独立的 discover_memory_providers(),模型提供者用懒惰的 _discover_providers()。如果明天需要添加第四个维度(比如"存储提供者"),需要再写一套发现机制。这是代码复制,但它是代码复制和独立演进之间的权衡——在 Hermes 当前的规模下,独立演进的价值超过了代码复制的成本。


通用插件的双路径发现

通用插件通过两种路径被发现:

路径一:目录扫描

~/.hermes/plugins/<name>/
./.hermes/plugins/<name>/   # 项目级插件
<repo>/plugins/<name>/      # 仓库内插件

每个插件目录需要一个 plugin.yaml(元数据)和 __init__.py(实现),__init__.py 必须暴露一个 register(ctx) 函数:

def register(ctx):
    ctx.register_tool(
        name="my_tool",
        handler=my_tool_handler,
        schema=MY_TOOL_SCHEMA,
        toolset="my_toolset",
    )
    ctx.register_cli_command(
        "my_plugin",
        build_argparse_subparser,
    )

路径二:pip entry points