第 4 讲:插件系统——PluginContext API、钩子系统、中间件与平台适配器模式
基于 Hermes Agent v0.16.0 源码 · 2026-06-17
一、插件系统:Hermes 的"能力边界扩展器"
前三讲我们建立了全局架构地图、深入了对话循环、解析了工具系统的三层架构。这一讲聚焦 Hermes 最灵活、最具扩展性的子系统——插件系统(Plugin System)。
插件是 Hermes "窄腰宽边"设计哲学的核心体现:核心保持稳定,能力通过插件向外延伸。从自定义工具、生命周期钩子、中间件,到平台适配器、模型提供者、图像/视频/TTS 后端——几乎所有新功能都通过插件机制接入,而非修改核心代码。
📦 源码仓库
https://github.com/NousResearch/hermes-agent
本地源码:~/.hermes/hermes-agent/
本讲核心文件:
hermes_cli/plugins.py(2046 行)— 插件管理器
hermes_cli/middleware.py(313 行)— 中间件契约
gateway/platforms/base.py(4884 行)— 平台适配器基类
gateway/platform_registry.py(260 行)— 平台注册表
plugins/(50+ 预置插件)— 插件目录
二、插件发现:四源扫描与优先级覆盖
Hermes 插件系统从四个来源自动发现插件,按优先级递增排列——后发现的同名插件覆盖先发现的:
插件发现优先级 (低 → 高,后者覆盖前者):
① Bundled plugins ← <repo>/plugins/<name>/
随 Hermes 发布的内置插件 (image_gen, web, video_gen 等)
② User plugins ← ~/.hermes/plugins/<name>/
用户自装插件,覆盖同名 bundled 插件
③ Project plugins ← ./.hermes/plugins/<name>/
项目级插件 (需 HERMES_ENABLE_PROJECT_PLUGINS=1)
覆盖用户插件
④ Pip/Entry-point ← importlib.metadata.entry_points()
通过 hermes_agent.plugins 入口点安装的 pip 包
最高优先级
源码: hermes_cli/plugins.py:1160-1308
_discover_and_load_inner():
1. _scan_directory(repo_plugins) → bundled
2. _scan_directory(user_dir) → user
3. _scan_directory(project_dir) → project (条件)
4. _scan_entry_points() → pip
5. winners = {manifest.key: manifest} → 后者覆盖前者
6. 对每个 winner: _load_plugin()
两种目录布局
插件扫描器 _scan_directory_level()(L1339-1393)支持两种混合布局:
Flat 布局 (standalone 插件):
plugins/disk-cleanup/plugin.yaml
→ key = "disk-cleanup"
Category 布局 (backend 插件):
plugins/image_gen/openai/plugin.yaml
→ key = "image_gen/openai"
深度限制: 最多 2 层 (plugins/a/b/ 有效, plugins/a/b/c/ 被跳过)
源码: hermes_cli/plugins.py:1339-1393
插件种类 (Plugin Kinds)
PluginManifest.kind 字段决定加载策略,共五种:
_VALID_PLUGIN_KINDS = {"standalone", "backend", "exclusive", "platform", "model-provider"}
┌──────────────┬──────────────────┬──────────────────────────────────────────┐
│ Kind │ 加载策略 │ 典型场景 │
├──────────────┼──────────────────┼──────────────────────────────────────────┤
│ standalone │ opt-in (plugins │ 自定义工具、钩子、slash 命令 │
│ │ .enabled 白名单) │ │
├──────────────┼──────────────────┼──────────────────────────────────────────┤
│ backend │ bundled 自动加载 │ image_gen/openai, web/brave, tts/edge │
│ │ user 需 opt-in │ 同一工具的多后端选择 │
├──────────────┼──────────────────┼──────────────────────────────────────────┤
│ exclusive │ 分类专属发现系统 │ memory 后端 (有独立 discovery 路径) │
│ │ 不参与通用加载 │ │
├──────────────┼──────────────────┼──────────────────────────────────────────┤
│ platform │ bundled 自动加载 │ IRC、Mattermost、Teams 等平台适配器 │
│ │ user 需 opt-in │ │
├──────────────┼──────────────────┼──────────────────────────────────────────┤
│ model- │ providers/ 专属 │ OpenRouter、Bedrock、Azure 等模型提供者 │
│ provider │ 延迟发现 │ (首次 get_provider_profile() 触发) │
└──────────────┴──────────────────┴──────────────────────────────────────────┘
源码: hermes_cli/plugins.py:232, 1240-1301智能类型推断:当 plugin.yaml 未声明 kind 时,PluginManager 会读取 __init__.py 前 8KB 源码,检测特征字符串自动推断——register_memory_provider → exclusive,register_provider + ProviderProfile → model-provider。源码 L1431-1461。
三、PluginContext API:插件与核心的唯一桥梁
PluginContext(L290-1081)是插件 register(ctx) 函数收到的唯一参数,它封装了所有插件可以向核心注册的能力:
PluginContext 方法总览 (13 个公开方法):
┌─────────────────────────────────────────────────────────────┐
│ PluginContext (hermes_cli/plugins.py:290-1081) │
│ │
│ 工具注册: │
│ register_tool(name, toolset, schema, handler, ...) │
│ → 委托给 tools.registry.register() │
│ │
│ 消息注入: │
│ inject_message(content, role="user") │
│ → 向 CLI 输入队列注入消息 (中断或排队) │
│ │
│ CLI 命令注册: │
│ register_cli_command(name, help, setup_fn, handler_fn) │
│ → 创建 "hermes <subcommand>" 终端命令 │
│ │
│ Slash 命令注册: │
│ register_command(name, handler, description, args_hint) │
│ → 注册会话内 /命令 (CLI + Gateway) │
│ │
│ 工具分发: │
│ dispatch_tool(tool_name, args, **kwargs) │
│ → 通过 registry.dispatch() 调用工具 │
│ │
│ 上下文引擎注册: │
│ register_context_engine(engine) │
│ → 替换内置 ContextCompressor (仅一个) │
│ │
│ 后端提供者注册 (6 种): │
│ register_image_gen_provider(provider) │
│ register_video_gen_provider(provider) │
│ register_web_search_provider(provider) │
│ register_browser_provider(provider) │
│ register_tts_provider(provider) │
│ register_transcription_provider(provider) │
│ │
│ 平台适配器注册: │
│ register_platform(name, label, factory, check_fn, ...) │
│ → 注册 Gateway 消息平台适配器 │
│ │
│ Slack 动作处理器: │
│ register_slack_action_handler(action_id, callback) │
│ → 注册 Slack Block Kit 交互处理器 │
│ │
│ 辅助任务注册: │
│ register_auxiliary_task(key, display_name, ...) │
│ → 注册 LLM 辅助任务 (独立 provider/model 路由) │
│ │
│ 钩子注册: │
│ register_hook(hook_name, callback) │
│ → 注册生命周期钩子回调 │
│ │
│ 中间件注册: │
│ register_middleware(kind, callback) │
│ → 注册行为变更中间件 │
│ │
│ 技能注册: │
│ register_skill(name, path, description) │
│ → 注册只读技能 ('<plugin>:<name>' 命名空间) │
│ │
│ LLM 访问 (属性): │
│ ctx.llm → PluginLlm 代理 (host-owned 模型调用) │
└─────────────────────────────────────────────────────────────┘
PluginLlm:安全隔离的 LLM 代理
插件通过 ctx.llm 属性访问宿主 LLM——不是原始 API key,而是一个受控代理:
# 源码: hermes_cli/plugins.py:301-316
@property
def llm(self) -> Any:
"""Return the plugin's PluginLlm facade.
Lets trusted plugins run host-owned chat or structured
completions against the user's active model and auth
without bringing their own provider keys.
Override capability is fail-closed by default and
gated through plugins.entries.<plugin_id>.llm.* config.
"""
if self._llm is None:
from agent.plugin_llm import PluginLlm
plugin_id = self.manifest.key or self.manifest.name
self._llm = PluginLlm(plugin_id=plugin_id)
return self._llm
设计要点:插件不需要自己的 API key,复用用户的模型配置;默认关闭覆盖能力(fail-closed),通过 config.yaml 中的 plugins.entries.<id>.llm.* 精细控制。
四、钩子系统 (Hook System):21 个生命周期注入点
Hermes 定义了 21 个合法钩子,覆盖 Agent 运行的每一个关键节点:
VALID_HOOKS (hermes_cli/plugins.py:128-170):
┌─ 工具调用生命周期 ─────────────────────────────────────┐
│ pre_tool_call → 工具调用前 (可 block/改写) │
│ post_tool_call → 工具调用后 (观察) │
│ transform_terminal_output → 终端输出变换 │
│ transform_tool_result → 工具结果变换 │
├────────────────────────────────────────────────────────┤
│ LLM 调用生命周期 │
│ pre_llm_call → LLM 请求前 (可注入 context) │
│ post_llm_call → LLM 响应后 (观察) │
│ transform_llm_output → LLM 输出变换 (词汇/风格) │
├────────────────────────────────────────────────────────┤
│ API 请求生命周期 │
│ pre_api_request → API 请求前 │
│ post_api_request → API 请求后 │
│ api_request_error → API 请求失败 │
├────────────────────────────────────────────────────────┤
│ 会话生命周期 │
│ on_session_start → 会话开始 │
│ on_session_end → 会话结束 │
│ on_session_finalize → 会话最终化 │
│ on_session_reset → 会话重置 │
├────────────────────────────────────────────────────────┤
│ 子 Agent 生命周期 │
│ subagent_start → 子 Agent 启动 │
│ subagent_stop → 子 Agent 停止 │
├────────────────────────────────────────────────────────┤
│ Gateway 生命周期 │
│ pre_gateway_dispatch → 消息分发前 (可 skip/rewrite) │
├────────────────────────────────────────────────────────┤
│ 审批生命周期 │
│ pre_approval_request → 审批请求前 (观察) │
│ post_approval_response → 审批响应后 (观察) │
└────────────────────────────────────────────────────────┘
钩子调用机制:隔离执行 + 错误容忍
invoke_hook()(L1658-1693)的核心设计:每个回调独立 try/except,一个插件崩溃不影响核心循环:
# 源码: hermes_cli/plugins.py:1658-1693
def invoke_hook(self, hook_name: str, **kwargs: Any) -> List[Any]:
"""Call all registered callbacks for hook_name.
Each callback is wrapped in its own try/except so a
misbehaving plugin cannot break the core agent loop.
"""
kwargs.setdefault("telemetry_schema_version", OBSERVER_SCHEMA_VERSION)
callbacks = self._hooks.get(hook_name, [])
results: List[Any] = []
for cb in callbacks:
try:
ret = cb(**kwargs)
if ret is not None:
results.append(ret)
except Exception as exc:
logger.warning(
"Hook '%s' callback %s raised: %s",
hook_name, getattr(cb, "__name__", repr(cb)), exc,
)
return results
关键钩子行为契约
pre_tool_call 钩子 — 工具拦截器:
返回 {"action": "block", "message": "原因"} → 工具被阻止
返回 None / 其他 → 工具正常执行
第一个有效的 block 指令胜出 (短路)
源码: hermes_cli/plugins.py:1860-1907
pre_llm_call 钩子 — 上下文注入:
返回 {"context": "召回文本..."} 或 "召回文本..."
上下文注入到 user message (非 system prompt!)
→ 保证 prompt cache 前缀不变 (系统提示词跨轮次稳定)
→ 注入上下文是瞬态的 (不持久化到 session DB)
源码: hermes_cli/plugins.py:1666-1676
pre_gateway_dispatch 钩子 — 消息门控:
返回 {"action": "skip", "reason": "..."} → 丢弃消息
返回 {"action": "rewrite", "text": "..."} → 替换消息
返回 {"action": "allow"} / None → 正常分发
源码: hermes_cli/plugins.py:148-155
transform_llm_output 钩子 — 输出变换:
返回非空字符串 → 替换 LLM 响应文本
返回 None/空字符串 → 保持原样
第一个非 None 字符串胜出
用途: 词汇/风格变换、敏感词过滤
五、中间件系统 (Middleware):行为变更管道
与"观察型"钩子不同,中间件可以改写请求或包装执行。四种中间件类型:
VALID_MIDDLEWARE (hermes_cli/middleware.py:29-34):
┌────────────────────────────────────────────────────┐
│ tool_request → 改写工具调用参数 │
│ tool_execution → 包装工具执行回调 │
│ llm_request → 改写 LLM 请求 payload │
│ llm_execution → 包装 LLM 执行回调 │
└────────────────────────────────────────────────────┘
Request 中间件返回 RequestMiddlewareResult:
payload: Any ← (可能已改写的) 请求载荷
original_payload: Any ← 原始载荷 (用于审计)
changed: bool ← 是否发生了变更
trace: List[Dict] ← 中间件执行追踪
源码: hermes_cli/middleware.py:37-74
关键实现细节:
_safe_copy() (L58-74): deepcopy 回退到浅拷贝
→ 容忍非 deepcopyable 对象 (clients, callbacks, file handles)
→ 保证中间件管道不因类型问题中断
钩子 vs 中间件的区别:钩子是观察者模式——报告发生了什么,不改变行为;中间件是责任链模式——可以改写请求 payload 或包装执行回调。同一插件可以同时注册两者。
六、平台适配器模式:Gateway 的多态骨架
BasePlatformAdapter(L1793-3000+)是所有消息平台适配器的抽象基类,定义了 Telegram、Discord、Slack、微信、飞书等 20+ 平台共享的接口契约:
BasePlatformAdapter 架构 (gateway/platforms/base.py:1793):
┌─────────────────────────────────────────────────────────────┐
│ BasePlatformAdapter (ABC) │
│ │
│ 能力标志 (Capability Flags): │
│ supports_code_blocks: bool = False ← Markdown 渲染 │
│ typed_command_prefix: str = "/" ← 命令前缀 │
│ │
│ 状态管理: │
│ _active_sessions: Dict[str, asyncio.Event] │
│ _pending_messages: Dict[str, MessageEvent] │
│ _session_tasks: Dict[str, asyncio.Task] │
│ _background_tasks: set[asyncio.Task] │
│ │
│ 消息管道: │
│ handle_message(event) ← 入口 │
│ _process_message(event) ← 核心处理 │
│ send(...) ← 发送消息 │
│ send_draft(...) ← 流式草稿预览 │
│ edit_message(...) ← 编辑已有消息 │
│ │
│ 流式渲染: │
│ render_message_event(event, sink) ← 文本/评论渲染 │
│ format_tool_event(event, mode) ← 工具事件渲染 │
│ │
│ 安全与策略: │
│ enforces_own_access_policy: bool ← 自有访问控制 │
│ _keep_typing() ← 输入指示器 │
│ _should_auto_tts_for_chat() ← 自动 TTS 策略 │
│ │
│ 错误处理: │
│ set_fatal_error(...) ← 标记致命错误 │
│ _mark_connected() ← 标记连接成功 │
│ has_fatal_error ← 错误状态查询 │
│ │
│ 子类实现 (20+): │
│ TelegramAdapter, DiscordAdapter, SlackAdapter, │
│ WeixinAdapter, FeishuAdapter, WhatsAppAdapter, │
│ IRCAdapter, MattermostAdapter, TeamsAdapter, ... │
└─────────────────────────────────────────────────────────────┘
平台注册表:动态发现替代硬编码
platform_registry(gateway/platform_registry.py)取代了传统 if/elif 链:
PlatformEntry (gateway/platform_registry.py:38-100):
@dataclass
class PlatformEntry:
name: str ← 配置标识 ("irc", "viber")
label: str ← 人类可读标签 ("IRC", "Viber")
adapter_factory: Callable ← 工厂函数 (非裸类!)
check_fn: Callable ← 依赖检查
validate_config: Optional[Callable] ← 配置校验
is_connected: Optional[Callable] ← 连接状态检查
required_env: list ← 环境变量需求
install_hint: str ← 安装提示
setup_fn: Optional[Callable] ← 交互式配置
source: str ← "builtin" | "plugin"
plugin_name: str ← 归属插件名
allowed_users_env: str ← 用户白名单
max_message_length: int ← 消息长度限制
pii_safe: bool ← PII 保护标志
emoji: str = "🌐" ← 显示图标
插件注册示例 (PluginContext.register_platform):
ctx.register_platform(
name="irc",
label="IRC",
adapter_factory=lambda cfg: IRCAdapter(cfg),
check_fn=lambda: True,
emoji="💬",
setup_fn=irc_interactive_setup,
)
→ 委托给 platform_registry.register(entry)
源码: hermes_cli/plugins.py:770-822
会话中断与任务追踪
BasePlatformAdapter 维护了一套精细的会话级中断系统:
会话中断机制 (base.py:1840-1849):
_active_sessions: Dict[str, asyncio.Event]
→ 每个活跃会话一个 Event,控制并发
→ /stop, /new, /reset 可中断当前处理
_session_tasks: Dict[str, asyncio.Task]
→ 精确映射 session → 当前处理 Task
→ 确保 finally 块清理正确的 guard
→ 避免旧任务清理新任务的 guard (竞态保护)
_pending_messages: Dict[str, MessageEvent]
→ 排队消息,按 session_key 索引
→ busy 时新消息进入队列而非丢弃
忙模式:
_busy_text_mode: str ← "interrupt" (默认) | "queue"
_busy_text_debounce_seconds: float ← 0.35s
_busy_text_hard_cap_seconds: float ← 1.0s
源码: base.py:1855-1864
七、插件加载:模块隔离与状态快照
插件加载是插件系统中最精妙的部分——命名空间隔离 + 注册归因快照:
插件加载流程 (hermes_cli/plugins.py:1520-1596):
_load_plugin(manifest):
1. 创建 LoadedPlugin 对象
2. 根据 source 选择加载方式:
- directory: _load_directory_module()
- entrypoint: _load_entrypoint_module()
3. 调用 module.register(ctx)
4. 快照归因 (关键!):
_tools_before = set(self._plugin_tool_names)
_hook_counts_before = {h: len(cbs) ...}
register_fn(ctx)
loaded.tools_registered = diff(后 - 前)
loaded.hooks_registered = diff(后 - 前)
loaded.middleware_registered = diff(后 - 前)
loaded.commands_registered = diff(后 - 前)
5. loaded.enabled = True
6. 异常 → loaded.error = str(exc), 不崩溃
模块导入隔离 (L1598-1634):
命名空间: hermes_plugins.<slug>
slug 规则: key.replace("/", "__").replace("-", "_")
→ "image_gen/openai" → "hermes_plugins.image_gen__openai"
→ 避免与 "tts/openai" 冲突
使用 importlib.util.spec_from_file_location()
→ 不污染 sys.modules,支持热重载
快照归因的意义:在调用 register(ctx) 之前快照全局注册表状态,之后 diff 得到精确的归属。修复了早期 bug——如果两个插件注册了同名钩子,旧方案会把共享名称只归于第一个插件,导致后续插件在 hermes plugins list 中少报。
八、PluginManager 全局单例与便捷接口
模块级单例 + 便捷函数构成了插件系统的外部 API:
全局接口 (hermes_cli/plugins.py:1795-1843):
_plugin_manager: Optional[PluginManager] = None
get_plugin_manager() → PluginManager
→ 懒加载单例,首次调用创建
discover_plugins(force=False) → None
→ 幂等发现,force=True 强制重扫
invoke_hook(hook_name, **kwargs) → List[Any]
→ 全局钩子调用入口
invoke_middleware(kind, **kwargs) → List[Any]
→ 全局中间件调用入口
has_hook(hook_name) → bool
has_middleware(kind) → bool
→ 快速检查是否有注册
get_plugin_context_engine() → ContextEngine | None
get_plugin_command_handler(name) → Callable | None
get_plugin_commands() → Dict[str, dict]
get_plugin_auxiliary_tasks() → List[Dict]
get_plugin_toolsets() → List[tuple]
→ 各类注册信息查询
set_thread_tool_whitelist(allowed: Set[str])
get_pre_tool_call_block_message(tool_name, args, ...)
→ 线程级工具白名单 + pre_tool_call 拦截
→ 用于子 Agent 安全隔离
安全模式与调试支持
安全模式 (L1127-1134):
HERMES_SAFE_MODE=1 → 跳过所有插件发现
→ 故障排除模式,禁用所有第三方代码
→ 标记为已发现,避免重试循环
调试模式 (L89-122):
HERMES_PLUGINS_DEBUG=1 → 详细插件发现日志
→ 输出: 扫描目录、解析清单、跳过原因
→ 输出: 每个 register() 注册了什么
→ 输出: 加载失败的完整 traceback
→ 双写: stderr + ~/.hermes/logs/agent.log
→ 测试支持: _install_plugin_debug_handler(force=True)
九、完整数据流:插件在 Agent 循环中的位置
插件系统在 Hermes 中的完整数据流:
启动阶段:
┌─────────────────────────────────────────────────┐
│ cli.py / gateway/run.py │
│ discover_plugins() │
│ → PluginManager._discover_and_load_inner() │
│ → 扫描 4 个来源 │
│ → 解析 plugin.yaml 清单 │
│ → 优先级覆盖 (bundled < user < project < pip)│
│ → 按 kind 分流加载 │
│ → _load_plugin() → register(ctx) │
│ → ctx.register_tool(...) │
│ → ctx.register_hook(...) │
│ → ctx.register_middleware(...) │
│ → ctx.register_platform(...) │
│ → ctx.register_image_gen_provider(...) │
│ → ctx.register_auxiliary_task(...) │
│ → ctx.register_skill(...) │
│ → ctx.register_command(...) │
└─────────────────────────────────────────────────┘
运行时 (每轮对话):
┌─────────────────────────────────────────────────┐
│ pre_gateway_dispatch 钩子 │
│ → skip / rewrite / allow │
│ │
│ on_session_start 钩子 │
│ │
│ pre_llm_call 钩子 │
│ → 注入 context 到 user message │
│ │
│ llm_request 中间件 │
│ → 改写 LLM 请求 payload │
│ │
│ pre_tool_call 钩子 │
│ → block / 放行 │
│ │
│ tool_request 中间件 │
│ → 改写工具参数 │
│ │
│ tool_execution 中间件 │
│ → 包装工具执行回调 │
│ │
│ 工具执行 → transform_tool_result 钩子 │
│ │
│ post_tool_call 钩子 │
│ │
│ llm_execution 中间件 │
│ → 包装 LLM 执行回调 │
│ │
│ post_llm_call 钩子 │
│ │
│ transform_llm_output 钩子 │
│ → 变换输出文本 │
│ │
│ on_session_end 钩子 │
└─────────────────────────────────────────────────┘
十、关键设计模式总结
插件系统设计模式
1. 四源发现 + 优先级覆盖 (Multi-Source Discovery)
bundled → user → project → pip
后者覆盖前者,用户可无缝替换内置插件
2. 种类路由 (Kind-Based Routing)
5 种 plugin kind 决定不同加载策略
自动类型推断 (源码扫描) 降低配置负担
3. Facade 模式 (PluginContext)
单一入口封装所有注册能力
插件只看到 ctx,不接触核心内部
4. 快照归因 (Snapshot Attribution)
register() 前后 diff 精确归属
修复多插件同名资源归属混乱
5. 命名空间隔离 (Namespace Isolation)
hermes_plugins.<slug> 避免模块冲突
importlib 动态加载支持热重载
6. 错误隔离 (Fault Isolation)
每个钩子回调独立 try/except
一个插件崩溃不影响核心循环
7. 能力标志 (Capability Flags)
supports_code_blocks, typed_command_prefix 等
适配器能力通过标志暴露,非硬编码分支
8. 钩子 vs 中间件 (Hooks vs Middleware)
钩子 = 观察者 (报告发生什么)
中间件 = 责任链 (改写/包装行为)
同一插件可同时注册两者
9. 安全降级 (Safe Mode)
HERMES_SAFE_MODE=1 跳过所有插件
故障排除时禁用第三方代码
10. 工厂模式 (Factory Pattern)
adapter_factory 而非裸类引用
插件可自定义初始化逻辑
十一、实战:编写一个最小插件
# 目录结构:~/.hermes/plugins/my-plugin/
plugin.yaml
__init__.py
plugin.yaml:
name: my-plugin
version: "1.0.0"
description: "我的第一个 Hermes 插件"
kind: standalone
provides_tools:
- my_custom_tool
provides_hooks:
- pre_tool_call
- post_llm_call__init__.py:
def register(ctx):
# 注册工具
ctx.register_tool(
name="my_custom_tool",
toolset="hermes-cli",
schema={
"type": "function",
"function": {
"name": "my_custom_tool",
"description": "A custom tool from my plugin",
"parameters": {
"type": "object",
"properties": {
"message": {"type": "string"}
},
"required": ["message"]
}
}
},
handler=lambda args: f"Echo: {args['message']}",
)
# 注册钩子
ctx.register_hook("pre_tool_call", lambda **kw: None)
ctx.register_hook("post_llm_call", lambda **kw: None)
# 注册 slash 命令
ctx.register_command(
name="mycmd",
handler=lambda raw_args: f"Got: {raw_args}",
description="My custom command",
)
启用: hermes plugins enable my-plugin
📖 下一讲预告:第 5 讲 — Provider 抽象层:ProviderProfile、模型路由、凭证池
Hermes Agent 源码解析 · 基于 NousResearch/hermes-agent
夜雨聆风