Hermes 架构全解Hermes Architecture, in Full第 9 章 · 14 章Chapter 9 of 14
全文目录Contents
  1. 0 · 项目全景与代码地图
    1. 0.1 这个软件是什么
    2. 0.2 目录地图
    3. 0.3 从这张地图能读出的三件事
    4. 0.4 一条消息的完整旅程
    5. 0.5 全文章节索引
  2. 1 · 网关层
    1. 1.1 它解决什么问题
    2. 1.2 平台适配器抽象基类
    3. 1.3 消息事件的归一化
    4. 1.4 网关主循环里的那些防御机制
    5. 1.5 状态消息与进度反馈
    6. 1.6 中断与恢复
    7. 1.7 网关内置钩子
    8. 1.8 这一层的代价
  3. 2 · 身份与会话路由
    1. 2.1 Profile:一个进程承载多个身份
    2. 2.2 四级路由与「具体度」打分
    3. 2.3 显式路由被拒绝的情况
    4. 2.4 SOUL.md:人格文件
    5. 2.5 会话路由:跨平台的连续性
    6. 2.6 智能体实例的缓存
  4. 3 · 主循环 ★
    1. 3.1 循环入口:三重预算闸门
    2. 3.2 每轮的准备工作:build_turn_context
    3. 3.3 中途插话:/steer
    4. 3.4 墙上时钟预算的收尾提醒
    5. 3.5 中断检查
    6. 3.6 步骤回调:给网关的观测点
    7. 3.7 循环里的其他状态
    8. 3.8 循环退出原因
    9. 3.9 一个可选的旁路:Codex 应用服务运行时
  5. 4 · 工具系统
    1. 4.1 最重要的设计:实现与投放分离
    2. 4.2 工具集的组合与解析
    3. 4.3 工具的中心分发
    4. 4.4 参数强制矫正层
    5. 4.5 工具错误消息的净化
    6. 4.6 工具结果的观测与钩子
    7. 4.7 委派上下文识别
    8. 4.8 工具定义的缓存
  6. 5 · 审批与安全红线 ★
    1. 5.1 整体结构
    2. 5.2 12 条硬红线
    3. 5.3 真正的难点:区分「命令」和「数据」
    4. 5.4 引号遮蔽:但要给「真会执行的部分」留后门
    5. 5.5 引号不能成为绕过手段
    6. 5.6 敏感路径与写入目标
    7. 5.7 sudo 标准输入守卫
    8. 5.8 性能:预编译的理由
    9. 5.9 被拦截命令的留存
    10. 5.10 智能审批:可选的模型判断
    11. 5.11 上下文感知:不同场景不同策略
    12. 5.12 这一层的定位:最外面也最薄
  7. 6 · 执行环境
    1. 6.1 七种可选环境
    2. 6.2 必须诚实说明的一件事
    3. 6.3 抽象基类里的共用逻辑
    4. 6.4 文件同步
    5. 6.5 环境的选择时机
  8. 7 · 上下文引擎 ★
    1. 7.1 它定义的是什么
    2. 7.2 生命周期
    3. 7.3 三个必须实现的方法
    4. 7.4 最精辟的设计:select 和 compress 是两个正交动词
    5. 7.5 后置观察钩子
    6. 7.6 其他可选钩子
    7. 7.7 默认参数值
    8. 7.8 用户可见状态的可控性
    9. 7.9 内置实现的体量对比
  9. 8 · 记忆系统 ★
    1. 8.1 记忆提供者接口
    2. 8.2 生命周期与钩子
    3. 8.3 接口的版本化契约
    4. 8.4 琐碎提问过滤器
    5. 8.5 记忆使用指示器
    6. 8.6 内置的全息记忆
    7. 8.7 存储层与信任分
    8. 8.8 SQLite 状态层
    9. 8.9 三种记忆的分工总结
  10. 9 · 插件系统
    1. 9.1 三个发现来源
    2. 9.2 插件能提供什么
    3. 9.3 最重要的设计:区分「可叠加能力」与「互斥策略」
    4. 9.4 插件的存储
    5. 9.5 插件与工具集的联动
    6. 9.6 插件钩子
    7. 9.7 MCP:另一条扩展路径
    8. 9.8 这套扩展体系的整体形状
  11. 10 · 委派与多智能体
    1. 10.1 为什么需要委派
    2. 10.2 深度限制:只允许一层
    3. 10.3 并发限制
    4. 10.4 子智能体的工具限制
    5. 10.5 子智能体的审批策略
    6. 10.6 运行中的控制
    7. 10.7 亲缘关系检查
    8. 10.8 看板:智能体之间的协作
    9. 10.9 委派系统为什么有 5,071 行
  12. 11 · 模型供应商与凭据池
    1. 11.1 为什么不能只支持一家
    2. 11.2 供应商适配器清单
    3. 11.3 凭据池:核心机制
    4. 11.4 PooledCredential:单个凭据的状态
    5. 11.5 「持久化」为什么必要
    6. 11.6 与其他机制的配合
    7. 11.7 供应商抽象要抽象到哪一层
  13. 12 · 定时任务
    1. 12.1 「cron」是什么
    2. 12.2 定时智能体的场景
    3. 12.3 最重要的一个类:CronPromptInjectionBlocked
    4. 12.4 定时任务的工具集收窄
    5. 12.5 失败处理
    6. 12.6 防重复执行
    7. 12.7 为什么 scheduler.py 有 367 KB
    8. 12.8 定时任务在整个架构里的位置
  14. 13 · 技能系统
    1. 13.1 技能是什么
    2. 13.2 前置元数据逐字段解释
    3. 13.3 渐进式披露:技能系统的核心机制
    4. 13.4 技能的支撑设施
    5. 13.5 技能 vs 工具 vs 插件
    6. 13.6 技能系统的一个隐含设计:可组合
    7. 13.7 15 个技能类别
    8. 13.8 全文回顾:Hermes 的整体形状
  1. 0 · The Project at a Glance, and a Map of the Code
    1. 0.1 What This Software Is
    2. 0.2 Directory Map
    3. 0.3 Three Things You Can Read Off This Map
    4. 0.4 The Full Journey of One Message
    5. 0.5 Chapter Index
  2. 1 · The Gateway Layer
    1. 1.1 The Problem It Solves
    2. 1.2 The Platform Adapter Abstract Base Class
    3. 1.3 Normalizing Message Events
    4. 1.4 The Defensive Mechanisms in the Gateway Main Loop
    5. 1.5 Status Messages and Progress Feedback
    6. 1.6 Interruption and Recovery
    7. 1.7 Built-in Gateway Hooks
    8. 1.8 What This Layer Costs
  3. 2 · Identity and Session Routing
    1. 2.1 Profiles: One Process, Many Identities
    2. 2.2 Four-Level Routing and the “Specificity” Score
    3. 2.3 When an Explicit Route Is Rejected
    4. 2.4 SOUL.md: The Persona File
    5. 2.5 Session Routing: Continuity Across Platforms
    6. 2.6 Caching Agent Instances
  4. 3 · The Main Loop ★
    1. 3.1 The Loop Entry: A Triple Budget Gate
    2. 3.2 Per-Turn Preparation: build_turn_context
    3. 3.3 Mid-Turn Interjection: /steer
    4. 3.4 The Wall-Clock Budget Wrap-Up Reminder
    5. 3.5 The Interrupt Check
    6. 3.6 The Step Callback: An Observation Point for the Gateway
    7. 3.7 Other State Inside the Loop
    8. 3.8 Why the Loop Exited
    9. 3.9 An Optional Bypass: The Codex App Server Runtime
  5. 4 · The Tool System
    1. 4.1 The Most Important Design Decision: Implementation Separated from Exposure
    2. 4.2 Composing and Resolving Toolsets
    3. 4.3 Central Tool Dispatch
    4. 4.4 The Argument Coercion Layer
    5. 4.5 Sanitizing Tool Error Messages
    6. 4.6 Observing Tool Results, and Hooks
    7. 4.7 Recognizing a Delegation Context
    8. 4.8 Caching Tool Definitions
  6. 5 · Approval and Safety Red Lines ★
    1. 5.1 The Overall Structure
    2. 5.2 The 12 Hard Red Lines
    3. 5.3 The Real Difficulty: Telling “Command” from “Data”
    4. 5.4 Quote Masking: But Leave a Way In for “the Part That Really Executes”
    5. 5.5 Quoting Is Not a Bypass
    6. 5.6 Sensitive Paths and Write Targets
    7. 5.7 The sudo Stdin Guard
    8. 5.8 Performance: Why Precompile
    9. 5.9 Retaining Blocked Commands
    10. 5.10 Smart Approval: An Optional Model Judgment
    11. 5.11 Context Awareness: Different Situations, Different Policies
    12. 5.12 Where This Layer Sits: Outermost, and Thinnest
  7. 6 · Execution Environments
    1. 6.1 Seven Environments to Choose From
    2. 6.2 One Thing That Has to Be Said Plainly
    3. 6.3 Shared Logic in the Abstract Base Class
    4. 6.4 File Synchronization
    5. 6.5 When the Environment Is Chosen
  8. 7 · The Context Engine ★
    1. 7.1 What It Defines
    2. 7.2 Lifecycle
    3. 7.3 The Three Methods You Must Implement
    4. 7.4 The Sharpest Design Decision: select and compress Are Two Orthogonal Verbs
    5. 7.5 The Post-Turn Observation Hook
    6. 7.6 Other Optional Hooks
    7. 7.7 Default Parameter Values
    8. 7.8 Control Over User-Visible Status
    9. 7.9 Size Comparison of the Built-in Implementation
  9. 8 · The Memory System ★
    1. 8.1 The Memory Provider Interface
    2. 8.2 Lifecycle and Hooks
    3. 8.3 The Interface's Versioned Contract
    4. 8.4 The Trivial-Prompt Filter
    5. 8.5 The Memory-Usage Indicator
    6. 8.6 The Built-in Holographic Memory
    7. 8.7 The Storage Layer and Trust Scores
    8. 8.8 The SQLite State Layer
    9. 8.9 Summing Up the Division of Labor Among Three Kinds of Memory
  10. 9 · The Plugin System
    1. 9.1 Three Discovery Sources
    2. 9.2 What a Plugin Can Provide
    3. 9.3 The Most Important Design Decision: “Stackable Capabilities” vs. “Mutually Exclusive Strategies”
    4. 9.4 Plugin Storage
    5. 9.5 How Plugins Tie into Toolsets
    6. 9.6 Plugin Hooks
    7. 9.7 MCP: The Other Extension Path
    8. 9.8 The Overall Shape of This Extension System
  11. 10 · Delegation and Multi-Agent
    1. 10.1 Why Delegation Is Needed
    2. 10.2 The Depth Limit: One Level Only
    3. 10.3 Concurrency Limits
    4. 10.4 Tool Restrictions on Subagents
    5. 10.5 Approval Policy for Subagents
    6. 10.6 In-Flight Control
    7. 10.7 The Lineage Check
    8. 10.8 Kanban: Collaboration Between Agents
    9. 10.9 Why the Delegation System Is 5,071 Lines
  12. 11 · Model Providers and the Credential Pool
    1. 11.1 Why You Can't Support Just One
    2. 11.2 The Provider Adapter Roster
    3. 11.3 The Credential Pool: The Core Mechanism
    4. 11.4 PooledCredential: The State of a Single Credential
    5. 11.5 Why “Persistent” Is Necessary
    6. 11.6 How It Works with Other Mechanisms
    7. 11.7 How Deep the Provider Abstraction Should Go
  13. 12 · Scheduled Tasks (Cron)
    1. 12.1 What “cron” Is
    2. 12.2 Scenarios for Scheduled Agents
    3. 12.3 The Single Most Important Class: CronPromptInjectionBlocked
    4. 12.4 Narrowing the Toolset for Scheduled Tasks
    5. 12.5 Failure Handling
    6. 12.6 Preventing Duplicate Runs
    7. 12.7 Why scheduler.py Is 367 KB
    8. 12.8 Where Scheduled Tasks Sit in the Overall Architecture
  14. 13 · The Skill System
    1. 13.1 What a Skill Is
    2. 13.2 The Front Matter, Field by Field
    3. 13.3 Progressive Disclosure: The Core Mechanism of the Skill System
    4. 13.4 The Infrastructure Around Skills
    5. 13.5 Skills vs. Tools vs. Plugins
    6. 13.6 An Implicit Design in the Skill System: Composability
    7. 13.7 The 15 Skill Categories
    8. 13.8 Looking Back Across the Book: The Overall Shape of Hermes

9 · 插件系统

plugins/,351 个文件。这一章讲第三方怎么在不改核心代码的前提下扩展系统

9.1 三个发现来源

~/.hermes/plugins/     用户级 —— 对这台机器上的所有项目生效
./.hermes/plugins/     项目级 —— 跟着代码仓库走,团队共享
pip entry points       包级   —— 用 pip install 安装某个包就自动生效
来源适用场景特点
用户级「我个人习惯用的工具」不进版本控制,不影响别人
项目级「这个项目需要的能力」提交进仓库,团队共享。新同事拉下代码就有
包级「发布给社区用的插件」走标准的 Python 包分发渠道,可以有版本、依赖、更新

「pip entry points」(入口点)是 Python 的标准机制:一个包可以在自己的元数据里声明「我提供了某类插件」,安装后框架自动发现,不需要用户手动注册。

9.2 插件能提供什么

plugins/
├── platforms/          22 个聊天平台适配器          → 第 1 章
├── memory/             8 种记忆后端                 → 第 8 章
├── context_engine/     上下文引擎                   → 第 7 章
├── model-providers/    模型供应商                   → 第 11 章
├── cron_providers/     定时任务提供者               → 第 12 章
├── kanban/             看板协作                     → 第 10 章
├── browser/            浏览器自动化
├── image_gen/          图像生成
├── video_gen/          视频生成
├── observability/      可观测性
├── dashboard_auth/     仪表盘认证
├── security-guidance/  安全指引
├── google_meet/        会议集成
├── spotify/            音乐
├── teams_pipeline/     Teams 流水线
├── disk-cleanup/       磁盘清理
├── hermes-achievements/ 成就系统
├── web/                网页相关
├── plugin_storage.py   ★ 插件的持久化存储
└── plugin_utils.py     ★ 插件工具函数

插件通过一套「上下文 API」向系统注册三类东西:工具、钩子、命令行子命令

9.3 最重要的设计:区分「可叠加能力」与「互斥策略」

这是插件系统设计里最容易漏掉的一个区分
可叠加能力互斥策略
例子 工具插件、平台适配器、图像生成后端 记忆提供者、上下文引擎
装 3 个会怎样 有 3 份能力,互不冲突。装得越多能力越强 系统不知道该听谁的
系统的处理 全部加载 「单选」—— 只允许激活一个

两处源码明确了这个约束:

// 上下文引擎(第 7 章)
「Selection is config-driven: `context.engine` in config.yaml.
  Default is "compressor" (the built-in). Only one engine is active.」

// 记忆提供者(第 8 章)
「The MemoryManager enforces a one-external-provider limit to prevent
  tool schema bloat and conflicting memory backends.
  Only one external provider runs at a time.」

如果不做这个区分会怎样

用户装了两个上下文引擎,都实现了 should_compress() 引擎 A:「该压缩了」 引擎 B:「不用压缩」 系统怎么办? · 听 A 的? → B 的作者会说「我的引擎被无视了」 · 都跑一遍?→ 压缩两次,第二次拿到的是第一次的结果,行为完全不可预测 · 随机选? → 每次行为不一样,无法排查 ★ 没有正确答案。所以必须在"装第二个"的那一刻就报错, 而不是留到运行时产生诡异行为。

在你自己的插件系统里,这个区分要在设计阶段就做出来。

判据很简单:「装两个的语义是『两份能力』还是『两个互相矛盾的答案』?」

如果是后者,就必须标记为单选,并且在加载第二个时明确报错。留到运行时会产生极难排查的问题 —— 因为症状是「行为和预期不一样」,而不是「报错了」。

9.4 插件的存储

plugins/plugin_storage.py

插件需要持久化自己的数据(配置、缓存、状态)。系统提供统一的存储抽象,而不是让每个插件自己决定往哪写。

这解决三个问题:

  • 路径统一 —— 备份、清理、迁移都有单一入口(呼应第 8 章的 backup_paths()
  • 隔离 —— 插件之间互相看不到对方的数据
  • 清理 —— 卸载插件时能完整清理它的数据

9.5 插件与工具集的联动

回顾第 4 章的工具集解析函数:

def _get_plugin_toolset_names() -> Set[str]        # 插件提供的工具集
def _get_registry_toolset_aliases() -> Dict[str, str]
def resolve_toolset(name, visited=None, *, include_registry: bool = True)

插件不只是「注册几个工具」,它可以注册一整个工具集。这样用户在配置里写 toolsets: [my_plugin_set] 就能启用插件的全部能力,而不用逐个列工具名。

那个 include_registry 参数说明:系统区分「内置工具集」和「注册表里的工具集(含插件的)」,某些场景下可以只解析内置的 —— 大概是为了在插件还没加载完时也能工作,或者为了安全场景下排除第三方工具。

9.6 插件钩子

插件可以挂钩到系统的几个关键点:

钩子时机与用途
pre_llm_call调模型前。注意它的约束:只能「追加到用户消息」,从不重写消息列表 —— 这是为了保护提示词缓存的前缀(第 7 章的 select_context() 才可以替换列表)
post_tool_call工具执行后。可以观察、记录、告警
agent:step智能体每走一步(第 3.6 节的步骤回调)
审批钩子第 5 章的 _fire_approval_hook,让插件参与安全决策
网关钩子gateway/hooks.py + gateway/builtin_hooks/,消息进出的节点

pre_llm_callselect_context 的权限差别

第 7 章的原文:「Unlike the pre_llm_call plugin hook (which appends to the user message and intentionally never rewrites the list, to preserve the cache prefix), select_context() may replace the message list.」

译:不同于 pre_llm_call 插件钩子(它只追加到用户消息,并且刻意从不重写列表,以保护缓存前缀),select_context() 可以替换整个消息列表。

这是一个分级授权的设计:

· 普通插件pre_llm_call)→ 只能追加,权限小,不会破坏缓存
· 上下文引擎select_context)→ 可以整个替换,权限大 —— 但它是「单选」的,用户明确选择了它,而且它的输出仍要过所有校验器

权限的大小和「用户是否明确授权」成正比。一个可以随便装十个的普通插件,不该有替换整个上下文的权力。

9.7 MCP:另一条扩展路径

除了插件,Hermes 还支持 MCP(Model Context Protocol,模型上下文协议)—— 一个让智能体接入外部工具服务的开放标准。

tools/mcp_tool.py       378 KB    MCP 客户端
mcp_serve.py            38 KB     ★ 把 Hermes 自己作为 MCP 服务暴露
optional-mcps/          65 个文件  内置的可选 MCP 服务
插件MCP
语言必须是 Python任何语言(跨进程通信)
进程同进程独立进程或远程服务
能力深 —— 可以挂钩子、注册工具集、替换核心策略浅 —— 主要是提供工具和资源
崩溃影响可能影响主进程隔离,不影响
生态Hermes 专属跨智能体产品通用

mcp_serve.py 那一项值得注意:Hermes 可以把自己作为 MCP 服务暴露出去。也就是说另一个智能体可以把 Hermes 当成一个工具来调用 —— 这让「智能体调用智能体」成为可能。

9.8 这套扩展体系的整体形状

按「权限大小」和「侵入深度」排列: ┌─ 最深、权限最大 ──────────────────────────────┐ │ 上下文引擎 / 记忆提供者 │ │ → 可替换核心策略,但【单选】,配置驱动 │ ├───────────────────────────────────────────────┤ │ 平台适配器 / 模型供应商 / 定时任务提供者 │ │ → 实现一个明确的抽象基类,可叠加 │ ├───────────────────────────────────────────────┤ │ 普通插件 │ │ → 注册工具、钩子、命令;钩子只能追加不能替换 │ ├───────────────────────────────────────────────┤ │ MCP 外部服务 │ │ → 跨进程、跨语言,只能提供工具和资源 │ ├───────────────────────────────────────────────┤ │ 技能(第 13 章) │ │ → 纯 Markdown 文本,零代码 │ └─ 最浅、权限最小 ──────────────────────────────┘

这个梯度是有意义的:扩展的门槛和它能造成的破坏成正比。

写一个技能只需要写 Markdown,任何人都能做,最多让智能体多知道一些操作步骤。
写一个上下文引擎需要理解整套契约(缓存不变式、生命周期、版本兼容),而它一旦出错会让整个系统的上下文管理失效。

系统通过「不同层级用不同机制」把这个梯度显式化了 —— 而不是提供一个万能的插件接口让所有人都能做所有事。

9 · The Plugin System

plugins/, 351 files. This chapter covers how third parties extend the system without touching core code.

9.1 Three Discovery Sources

~/.hermes/plugins/     user-level    — applies to every project on this machine
./.hermes/plugins/     project-level — travels with the repo, shared by the team
pip entry points       package-level — pip install a package and it takes effect automatically
SourceWhere it fitsCharacteristics
User-level“Tools I personally like to use”Not under version control; affects nobody else
Project-level“Capabilities this project needs”Committed to the repo, shared by the team. A new teammate pulls the code and has them
Package-level“Plugins published for the community”Goes through the standard Python packaging channel, with versions, dependencies, and updates

“pip entry points” are a standard Python mechanism: a package declares in its own metadata “I provide this kind of plugin,” and after installation the framework discovers it automatically, with no manual registration by the user.

9.2 What a Plugin Can Provide

plugins/
├── platforms/          22 chat platform adapters        → chapter 1
├── memory/             8 memory backends                → chapter 8
├── context_engine/     context engines                  → chapter 7
├── model-providers/    model providers                  → chapter 11
├── cron_providers/     scheduled-task providers         → chapter 12
├── kanban/             kanban collaboration             → chapter 10
├── browser/            browser automation
├── image_gen/          image generation
├── video_gen/          video generation
├── observability/      observability
├── dashboard_auth/     dashboard authentication
├── security-guidance/  security guidance
├── google_meet/        meeting integration
├── spotify/            music
├── teams_pipeline/     Teams pipeline
├── disk-cleanup/       disk cleanup
├── hermes-achievements/ achievement system
├── web/                web-related
├── plugin_storage.py   ★ persistent storage for plugins
└── plugin_utils.py     ★ plugin utility functions

Through a “context API,” a plugin registers three kinds of things with the system: tools, hooks, and CLI subcommands.

9.3 The Most Important Design Decision: “Stackable Capabilities” vs. “Mutually Exclusive Strategies”

This is the distinction most easily missed when designing a plugin system
Stackable capabilityMutually exclusive strategy
Examples Tool plugins, platform adapters, image-generation backends Memory providers, context engines
What happens if you install 3 You have 3 capabilities that don't conflict. The more you install, the more you can do The system doesn't know which one to listen to
How the system handles it Loads them all “Single-select” — only one may be active

Two places in the source make this constraint explicit:

// Context engine (chapter 7)
“Selection is config-driven: `context.engine` in config.yaml.
  Default is "compressor" (the built-in). Only one engine is active.”

// Memory provider (chapter 8)
“The MemoryManager enforces a one-external-provider limit to prevent
  tool schema bloat and conflicting memory backends.
  Only one external provider runs at a time.”

What happens if you don't make this distinction

The user installs two context engines, both implementing should_compress() Engine A: "Time to compact" Engine B: "No need to compact" What does the system do? · Listen to A? → B's author says "my engine is being ignored" · Run both? → compacts twice; the second run gets the first run's output; behavior is totally unpredictable · Pick at random? → different behavior every time; impossible to debug ★ There is no right answer. So it has to fail the moment the "second one is installed," rather than wait until runtime and produce bizarre behavior.

In your own plugin system, make this distinction at the design stage.

The test is simple: “If two are installed, does that mean ‘two capabilities’ or ‘two contradictory answers’?”

If it's the latter, you must mark it single-select and fail explicitly when a second one is loaded. Leaving it to runtime produces problems that are extremely hard to track down — because the symptom is “behavior differs from expectations,” not “an error was thrown.”

9.4 Plugin Storage

plugins/plugin_storage.py

Plugins need to persist their own data (configuration, caches, state). The system provides a unified storage abstraction rather than letting each plugin decide where to write.

This solves three problems:

  • Unified paths — backup, cleanup, and migration all have a single entry point (echoing backup_paths() from chapter 8)
  • Isolation — plugins can't see each other's data
  • Cleanup — uninstalling a plugin can remove all of its data cleanly

9.5 How Plugins Tie into Toolsets

Recall the toolset resolution functions from chapter 4:

def _get_plugin_toolset_names() -> Set[str]        # toolsets provided by plugins
def _get_registry_toolset_aliases() -> Dict[str, str]
def resolve_toolset(name, visited=None, *, include_registry: bool = True)

A plugin doesn't just “register a few tools”; it can register an entire toolset. That way a user can write toolsets: [my_plugin_set] in their config to enable everything the plugin offers, without listing tool names one by one.

That include_registry parameter tells you something: the system distinguishes “built-in toolsets” from “toolsets in the registry (including plugins')”, and in some scenarios it can resolve only the built-in ones — presumably so things still work before plugins have finished loading, or to exclude third-party tools in security-sensitive contexts.

9.6 Plugin Hooks

Plugins can hook into several key points in the system:

HookWhen and what for
pre_llm_callBefore calling the model. Note its constraint: it may only “append to the user message,” never rewrite the message list — this protects the prompt-cache prefix (only select_context() from chapter 7 may replace the list)
post_tool_callAfter a tool runs. Can observe, log, or alert
agent:stepEvery step the agent takes (the step callback from section 3.6)
Approval hooks_fire_approval_hook from chapter 5, letting plugins take part in safety decisions
Gateway hooksgateway/hooks.py + gateway/builtin_hooks/, the points where messages enter and leave

The permission gap between pre_llm_call and select_context

From chapter 7's source: “Unlike the pre_llm_call plugin hook (which appends to the user message and intentionally never rewrites the list, to preserve the cache prefix), select_context() may replace the message list.”

In plain terms: unlike the pre_llm_call plugin hook (which only appends to the user message and deliberately never rewrites the list, to preserve the cache prefix), select_context() may replace the entire message list.

This is tiered authorization by design:

· Ordinary plugins (pre_llm_call) → append only; small privilege; can't break the cache
· Context engines (select_context) → may replace the whole thing; large privilege — but they are “single-select,” the user explicitly chose them, and their output still passes through every validator

The size of the privilege scales with “did the user explicitly authorize it.” An ordinary plugin that you can casually install ten of should never have the power to replace the entire context.

9.7 MCP: The Other Extension Path

Besides plugins, Hermes also supports MCP (Model Context Protocol) — an open standard for connecting agents to external tool services.

tools/mcp_tool.py       378 KB    MCP client
mcp_serve.py            38 KB     ★ exposes Hermes itself as an MCP server
optional-mcps/          65 files  built-in optional MCP servers
PluginMCP
LanguageMust be PythonAny language (cross-process communication)
ProcessSame processSeparate process or remote service
ReachDeep — can attach hooks, register toolsets, replace core strategiesShallow — mainly provides tools and resources
Blast radius of a crashCan take down the main processIsolated; no effect
EcosystemHermes-specificShared across agent products

The mcp_serve.py entry deserves attention: Hermes can expose itself as an MCP server. In other words, another agent can call Hermes as a tool — which makes “agents calling agents” possible.

9.8 The Overall Shape of This Extension System

Ordered by "size of privilege" and "depth of intrusion": ┌─ Deepest, most privileged ───────────────────────────────────┐ │ Context engine / memory provider │ │ → replaces a core strategy; SINGLE-SELECT, config-driven │ ├──────────────────────────────────────────────────────────────┤ │ Platform adapters / model providers / cron providers │ │ → implements a clear abstract base class; stackable │ ├──────────────────────────────────────────────────────────────┤ │ Ordinary plugins │ │ → tools, hooks, CLI commands; hooks append, never replace │ ├──────────────────────────────────────────────────────────────┤ │ MCP external services │ │ → cross-process, cross-language; tools and resources only │ ├──────────────────────────────────────────────────────────────┤ │ Skills (chapter 13) │ │ → pure Markdown text, zero code │ └─ Shallowest, least privileged ───────────────────────────────┘

This gradient means something: the barrier to entry for an extension scales with the damage it can do.

Writing a skill just means writing Markdown; anyone can do it, and at most it teaches the agent a few more procedures.
Writing a context engine requires understanding the whole contract (the cache invariant, the lifecycle, version compatibility), and once it goes wrong, context management for the entire system breaks down.

The system makes this gradient explicit by “using different mechanisms at different tiers” — rather than offering one all-purpose plugin interface that lets everyone do everything.