词元之母TOK.MOM - 平台充值汇率 1:1 即 1 人民币充值到账 1 美元,支持一个 Key 调用近 600+ 海内外模型,限时特价模型低至 1 折,欢迎上岸!
| 系统 | 注册方式 | 运行环境 | 使用场景 |
|---|---|---|---|
| Gateway hooks | ~/.hermes/hooks/ 下的 HOOK.yaml + handler.py | 仅 Gateway | 日志、告警、webhook |
| Plugin hooks | 插件中的 ctx.register_hook() | CLI + Gateway | 工具拦截、指标采集、护栏 |
| Shell hooks | ~/.hermes/config.yaml 中 hooks: 块指向的 shell 脚本 | CLI + Gateway | 用于阻断、自动格式化、上下文注入的即插即用脚本 |
~/.hermes/hooks/ 下的一个目录,包含两个文件:~/.hermes/hooks/
└── my-hook/
├── HOOK.yaml # 声明要监听的事件
└── handler.py # Python 处理函数events 列表决定哪些事件会触发你的处理器。可以订阅任意事件组合,包括 command:* 这样的通配符。handleevent_type(字符串)和 context(字典)async def 或普通 def——两者均可| 事件 | 触发时机 | Context 键 |
|---|---|---|
gateway:startup | Gateway 进程启动 | platforms(活跃平台名称列表) |
session:start | 新消息会话创建 | platform、user_id、session_id、session_key |
session:end | 会话结束(重置前) | platform、user_id、session_key |
session:reset | 用户执行 /new 或 /reset | platform、user_id、session_key |
agent:start | Agent 开始处理消息 | platform、user_id、session_id、message |
agent:step | 工具调用循环的每次迭代 | platform、user_id、session_id、iteration、tool_names |
agent:end | Agent 完成处理 | platform、user_id、session_id、message、response |
command:* | 任意斜杠命令执行 | platform、user_id、command、args |
command:* 的处理器会在任何 command: 事件(command:model、command:reset 等)触发时执行。通过单个订阅即可监控所有斜杠命令。~/.hermes/BOOT.md 放置一个 Markdown 检查清单,让 agent 在每次 gateway 启动时执行一次。适用于"每次启动时检查隔夜 cron 失败情况,若有失败则在 Discord 上通知我",或"汇总过去 24 小时的 deploy.log 并发布到 Slack #ops"等场景。~/.hermes/BOOT.md 放置一个包含自然语言启动指令的文件。gateway:startup 的 gateway hook,它会生成一个一次性 agent,使用 gateway 已解析的模型和凭据,执行 BOOT.md 中的指令。[SILENT] 约定,让 agent 在没有内容需要汇报时选择不发送消息。~/.hermes/BOOT.md。像给人类助手下达指令一样编写:~/.hermes/hooks/boot-md/
├── HOOK.yaml
└── handler.py~/.hermes/hooks/boot-md/HOOK.yaml~/.hermes/hooks/boot-md/handler.py_resolve_gateway_model() 读取 gateway 当前配置的模型。_resolve_runtime_agent_kwargs() 以与普通 gateway 轮次相同的方式解析 provider 凭据——包括 API 密钥、base URL、OAuth token 和凭据池。AIAgent() 会回退到内置默认值,并在任何非默认端点上返回 401。Running BOOT.md (N chars),随后是 boot-md completed: ...(agent 执行内容的摘要)或 boot-md completed (nothing to report)(agent 回复了 [SILENT])。~/.hermes/BOOT.md 即可禁用检查清单——hook 保持加载状态,但在文件不存在时会静默跳过。datetime.now().weekday() 进行判断("如果是周一,还需检查每周部署日志")。指令是自由格式文本,agent 能推理的内容都可以使用。STARTUP.md、MORNING.md 等),并为每个文件注册独立的 hook 目录。AIAgent,直接通过 httpx 在处理器中发送固定通知。更轻量、更快速,且无 provider 依赖。HookRegistry.discover_and_load() 扫描 ~/.hermes/hooks/HOOK.yaml + handler.py 的子目录都会被动态加载hooks.emit() 触发所有匹配的处理器register() 函数中的 ctx.register_hook() 以编程方式注册。**kwargs 以保持向前兼容性——未来版本可能会在不破坏插件的情况下添加新参数。pre_tool_call 可以阻断工具,pre_llm_call 可以注入上下文到 LLM 调用中。其他所有 hook 均为即发即忘的观察者。| Hook | 触发时机 | 返回值 |
|---|---|---|
pre_tool_call | 任意工具执行前 | {"action": "block", "message": str} 用于否决调用 |
post_tool_call | 任意工具返回后 | 忽略 |
pre_llm_call | 每轮一次,工具调用循环前 | {"context": str} 用于在用户消息前追加上下文 |
post_llm_call | 每轮一次,工具调用循环后 | 忽略 |
on_session_start | 新会话创建(仅第一轮) | 忽略 |
on_session_end | 会话结束 | 忽略 |
on_session_finalize | CLI/gateway 销毁活跃会话(刷新、保存、统计) | 忽略 |
on_session_reset | Gateway 换入新会话 key(如 /new、/reset) | 忽略 |
subagent_stop | delegate_task 子 agent 退出 | 忽略 |
pre_gateway_dispatch | Gateway 收到用户消息,认证和分发前 | {"action": "skip" | "rewrite" | "allow", ...} 用于影响流程 |
pre_approval_request | 危险命令需要用户审批,提示/通知发送前 | 忽略 |
post_approval_response | 用户响应审批提示(或超时) | 忽略 |
transform_tool_result | 任意工具返回后,结果交还给模型前 | str 替换结果,None 保持不变 |
transform_terminal_output | terminal 工具内部,截断/ANSI 剥离/脱敏前 | str 替换原始输出,None 保持不变 |
transform_llm_output | 工具调用循环完成后,最终响应交付前 | str 替换响应文本,None/空值保持不变 |
pre_tool_call| 参数 | 类型 | 描述 |
|---|---|---|
tool_name | str | 即将执行的工具名称(如 "terminal"、"web_search"、"read_file") |
args | dict | 模型传递给工具的参数 |
task_id | str | 会话/任务标识符。未设置时为空字符串。 |
model_tools.py 中的 handle_function_call() 内,工具处理器运行前。每次工具调用触发一次——若模型并行调用 3 个工具,则触发 3 次。message 作为返回给模型的错误短路该工具调用。第一个匹配的 block 指令生效(Python 插件优先,然后是 shell hooks)。任何其他返回值均被忽略,因此仅作观察用途的现有回调无需修改。post_tool_call| 参数 | 类型 | 描述 |
|---|---|---|
tool_name | str | 刚刚执行的工具名称 |
args | dict | 模型传递给工具的参数 |
result | str | 工具的返回值(始终为 JSON 字符串) |
task_id | str | 会话/任务标识符。未设置时为空字符串。 |
duration_ms | int | 工具分发耗时,单位毫秒(使用 time.monotonic() 在 registry.dispatch() 前后测量)。 |
model_tools.py 中的 handle_function_call() 内,工具处理器返回后。每次工具调用触发一次。若工具抛出未处理异常,不会触发(错误被捕获并以错误 JSON 字符串返回,post_tool_call 以该错误字符串作为 result 触发)。pre_llm_call| 参数 | 类型 | 描述 |
|---|---|---|
session_id | str | 当前会话的唯一标识符 |
user_message | str | 本轮用户的原始消息(技能注入前) |
conversation_history | list | 完整消息列表的副本(OpenAI 格式:[{"role": "user", "content": "..."}]) |
is_first_turn | bool | 新会话的第一轮为 True,后续轮次为 False |
model | str | 模型标识符(如 "anthropic/claude-sonnet-4.6") |
platform | str | 会话运行环境:"cli"、"telegram"、"discord" 等 |
run_agent.py 中的 run_conversation() 内,上下文压缩后、主 while 循环前。每次 run_conversation() 调用触发一次(即每个用户轮次一次),而非工具循环内每次 API 调用触发一次。"context" 键的字典,或非空的普通字符串,该文本会追加到当前轮次的用户消息。返回 None 表示不注入。post_llm_call| 参数 | 类型 | 描述 |
|---|---|---|
session_id | str | 当前会话的唯一标识符 |
user_message | str | 本轮用户的原始消息 |
assistant_response | str | Agent 本轮的最终文本响应 |
conversation_history | list | 轮次完成后完整消息列表的副本 |
model | str | 模型标识符 |
platform | str | 会话运行环境 |
run_agent.py 中的 run_conversation() 内,工具循环以最终响应退出后。受 if final_response and not interrupted 保护——因此当用户在轮次中途中断,或 agent 在未产生响应的情况下达到迭代上限时,不会触发。on_session_start| 参数 | 类型 | 描述 |
|---|---|---|
session_id | str | 新会话的唯一标识符 |
model | str | 模型标识符 |
platform | str | 会话运行环境 |
run_agent.py 中的 run_conversation() 内,新会话第一轮期间——具体在系统 prompt 构建后、工具循环开始前。检查条件为 if not conversation_history(无历史消息 = 新会话)。on_session_endrun_conversation() 调用结束时触发,无论结果如何。若用户在 agent 处理过程中退出,也会从 CLI 的退出处理器触发。| 参数 | 类型 | 描述 |
|---|---|---|
session_id | str | 会话的唯一标识符 |
completed | bool | Agent 产生最终响应时为 True,否则为 False |
interrupted | bool | 轮次被中断时为 True(用户发送新消息、/stop 或退出) |
model | str | 模型标识符 |
platform | str | 会话运行环境 |
run_agent.py — 每次 run_conversation() 调用结束时,所有清理完成后。始终触发,即使轮次出错。cli.py — CLI 的 atexit 处理器中,但仅当 agent 在退出时处于处理中状态(_agent_running=True)。这捕获了处理过程中的 Ctrl+C 和 /exit。此时 completed=False,interrupted=True。on_session_start 中初始化的资源。on_session_finalize/new、gateway GC 了空闲会话,或 CLI 在 agent 活跃时退出。这是在会话身份消失前刷新与该会话绑定状态的最后机会。| 参数 | 类型 | 描述 |
|---|---|---|
session_id | str 或 None | 即将销毁的会话 ID。若无活跃会话则可能为 None。 |
platform | str | "cli" 或消息平台名称("telegram"、"discord" 等)。 |
cli.py(/new / CLI 退出时)和 gateway/run.py(会话重置或 GC 时)。在 gateway 侧始终与 on_session_reset 配对。on_session_reset/new、/reset、/clear,或适配器在空闲窗口后选择了新会话。这让插件能在不等待下一个 on_session_start 的情况下响应对话状态已被清除这一事实。| 参数 | 类型 | 描述 |
|---|---|---|
session_id | str | 新会话的 ID(已轮换为新值)。 |
platform | str | 消息平台名称。 |
gateway/run.py 中,新会话 key 分配后、下一条入站消息处理前立即触发。在 gateway 侧,顺序为:on_session_finalize(old_id) → 切换 → on_session_reset(new_id) → 第一条入站消息时的 on_session_start(new_id)。session_id 为键的每会话缓存、发出"会话已轮换"分析事件、初始化新状态桶。subagent_stopdelegate_task 完成后,每个子 agent 触发一次。无论你委托了单个任务还是三个任务的批次,此 hook 对每个子 agent 各触发一次,在父线程上串行执行。| 参数 | 类型 | 描述 |
|---|---|---|
parent_session_id | str | 委托父 agent 的会话 ID |
child_role | str | None | 子 agent 上设置的编排角色标签(若功能未启用则为 None) |
child_summary | str | None | 子 agent 返回给父 agent 的最终响应 |
child_status | str | "completed"、"failed"、"interrupted" 或 "error" |
duration_ms | int | 运行子 agent 的挂钟时间,单位毫秒 |
tools/delegate_tool.py 中,ThreadPoolExecutor.as_completed() 排空所有子 future 后。触发被编排到父线程,因此 hook 作者无需考虑并发回调执行问题。subagent_stop 每轮会触发多次。保持回调快速执行;将耗时操作推送到后台队列。pre_gateway_dispatchMessageEvent 触发一次,在内部事件守卫之后、认证/配对和 agent 分发之前。这是 gateway 级消息流策略(只听不回窗口、人工接管、按聊天路由等)的拦截点,这些策略不适合放在任何单一平台适配器中。| 参数 | 类型 | 描述 |
|---|---|---|
event | MessageEvent | 标准化的入站消息(包含 .text、.source、.message_id、.internal 等)。 |
gateway | GatewayRunner | 活跃的 gateway 运行器,插 件可调用 gateway.adapters[platform].send(...) 进行旁路回复(所有者通知等)。 |
session_store | SessionStore | 用于通过 session_store.append_to_transcript(...) 静默摄入转录。 |
gateway/run.py 中的 GatewayRunner._handle_message() 内,is_internal 计算后立即触发。内部事件完全跳过此 hook(它们是系统生成的——后台进程完成等——不得被面向用户的策略拦截)。None 或字典。第一个被识别的 action 字典生效;其余插件结果被忽略。插件回调中的异常会被捕获并记录;gateway 在出错时始终回退到正常分发。| 返回值 | 效果 |
|---|---|
{"action": "skip", "reason": "..."} | 丢弃消息——无 agent 回复、无配对流程、 无认证。假定插件已处理(如静默摄入到转录)。 |
{"action": "rewrite", "text": "new text"} | 替换 event.text,然后以修改后的事件继续正常分发。适用于将缓冲的环境消息合并为单个 prompt。 |
{"action": "allow"} / None | 正常分发——运行完整的认证/配对/agent 循环链。 |
pre_approval_request| 参数 | 类型 | 描述 |
|---|---|---|
command | str | 等待 审批的 shell 命令 |
description | str | 命令被标记的人类可读原因(多个模式匹配时合并) |
pattern_key | str | 触发审批的主要模式键(如 "rm_rf"、"sudo") |
pattern_keys | list[str] | 所有匹配的模式键 |
session_key | str | 会话标识符,用于按聊天限定通知范围 |
surface | str | 交互式 CLI/TUI 提示为 "cli",异步平台审批为 "gateway" |
pre_tool_call 在工具到达审批系统前阻断它。post_approval_responsepre_approval_request 相同的 kwargs,另加:| 参数 | 类型 | 描述 |
|---|---|---|
choice | str | "once"、"session"、"always"、"deny" 或 "timeout" 之一 |
transform_tool_result| 参数 | 类型 | 描述 |
|---|---|---|
tool_name | str | 产生结果的工具(read_file、web_extract、delegate_task 等)。 |
arguments | dict | 模型调用工具时传入的参数。 |
result | str | 工具的原始结果字符串,截断和 ANSI 剥离后。 |
task_id | str | None | 在 RL/基准测试环境中运行时的任务/会话 ID。 |
str 替换结果(返回的字符串即模型看到的内容),None 保持不变。web_extract 输出中脱敏组织特定的 PII、为长 JSON 工具响应添加摘要头、向 read_file 结果注入检索增强提示、将 delegate_task 子 agent 报告重写为项目特定 schema。transform_terminal_output——它范围更窄,在管道中运行更早(截断前、脱敏前)。transform_terminal_outputterminal 工具的前台输出管道内触发,在默认的 50 KB 截断、ANSI 剥离和密钥脱敏之前。允许插件在任何下游处理之前重写 shell 命令的原始 stdout/stderr。| 参数 | 类型 | 描述 |
|---|---|---|
command | str | 产生输出的 shell 命令。 |
output | str | 原始合并的 stdout/stderr(可能非常大——截断在 hook 之后发生)。 |
exit_code | int | 进程退出码。 |
cwd | str | 命令运行的工作目录。 |
str 替换输出,None 保持不变。du -ah、find、tree)、用项目特定标记标注输出以便下游 hook 处理、剥离在运行间抖动并破坏 prompt 缓存的计时噪声。transform_tool_result(覆盖所有其他工具)配合使用效果更佳。transform_llm_output| 参数 | 类型 | 描述 |
|---|---|---|
response_text | str | 本轮 assistant 的最终响应文本。 |
session_id | str | 本次对话的会话 ID(一次性运行时可能为空)。 |
model | str | 产生响应的模型名称(如 anthropic/claude-sonnet-4.6)。 |
platform | str | 交付平台(cli、telegram、discord 等;未设置时为空)。 |
str 替换响应文本,None 或空字符串保持不变。当多个插件注册时,第一个非空字符串生效——与 transform_tool_result 保持一致。cli-config.yaml 中声明 shell 脚本 hook,Hermes 会在对应的插件 hook 事件触发时将其作为子进程运行——在 CLI 和 gateway 会话中均适用。无需编写 Python 插件。terminal 命令、执行按目录策略、要求对破坏性的 write_file / patch 操作进行审批。git status 输出、当前星期几或检索到的文档(参见 pre_llm_call)。subagent_stop)或会话开始(on_session_start)时写入日志行。hermes_cli/main.py)和 gateway 启动(gateway/run.py)时调用 agent.shell_hooks.register_from_config(cfg) 来注册。它们与 Python 插件 hook 自然组合——两者都流经同一个分发器。| 维度 | Shell hooks | Plugin hooks | Gateway hooks |
|---|---|---|---|
| 声明位置 | ~/.hermes/config.yaml 中的 hooks: 块 | 插件 plugin.yaml 中的 register() | HOOK.yaml + handler.py 目录 |
| 存放位置 | ~/.hermes/agent-hooks/(约定) | ~/.hermes/plugins/<name>/ | ~/.hermes/hooks/<name>/ |
| 语言 | 任意(Bash、Python、Go 二进制等) | 仅 Python | 仅 Python |
| 运行环境 | CLI + Gateway | CLI + Gateway | 仅 Gateway |
| 事件 | VALID_HOOKS(含 subagent_stop) | VALID_HOOKS | Gateway 生命周期(gateway:startup、agent:*、command:*) |
| 可阻断工具调用 | 是(pre_tool_call) | 是(pre_tool_call) | 否 |
| 可注入 LLM 上下文 | 是(pre_llm_call) | 是(pre_llm_call) | 否 |
| 授权 | 每个 (event, command) 对首次使用时提示 | 隐式(Python 插件信任) | 隐式(目录信任) |
| 进程间隔离 | 是(子进程) | 否(进程内) | 否(进程内) |
command 会跳过并发出警告。timeout > 300 会被截断并发出警告。{
"hook_event_name": "pre_tool_call",
"tool_name": "terminal",
"tool_input": {"command": "rm -rf /"},
"session_id": "sess_abc123",
"cwd": "/home/user/project",
"extra": {"task_id": "...", "tool_call_id": "..."}
}pre_llm_call、subagent_stop、会话生命周期),tool_name 和 tool_input 为 null。extra 字典携带所有事件特定的 kwargs(user_message、conversation_history、child_role、duration_ms 等)。不可序列化的值会被字符串化而非省略。// Block a pre_tool_call (both shapes accepted; normalised internally):
{"decision": "block", "reason": "Forbidden: rm -rf"} // Claude-Code style
{"action": "block", "message": "Forbidden: rm -rf"} // Hermes-canonical
// Inject context for pre_llm_call:
{"context": "Today is Friday, 2026-04-17"}
// Silent no-op — any empty / non-matching output is fine:read_file 调用会读取格式化后的版本。terminal 命令git status(Claude-Code UserPromptSubmit 等效)UserPromptSubmit 事件在 Hermes 中没有对应的独立事件——pre_llm_call 在相同位置触发,且已支持上下文注入。在此使用即可。(event, command) 对在 Hermes 首次遇到时会提示用户审批,然后将决定持久化到 ~/.hermes/shell-hooks-allowlist.json。后续运行(CLI 或 gateway)跳过提示。--accept-hooks 标志(如 hermes --accept-hooks chat)HERMES_ACCEPT_HOOKS=1 环境变量cli-config.yaml 中的 hooks_auto_accept: truehermes hooks doctor 会标记 mtime 漂移,以便你发现编辑并决定是否重新审批。hermes hooks CLI| 命令 | 功能 |
|---|---|
hermes hooks list | 列出已配置的 hook,包含 matcher、超时和授权状态 |
hermes hooks test <event> [--for-tool X] [--payload-file F] | 对合成载荷触发所有匹配的 hook 并打印解析后的响应 |
hermes hooks revoke <command> | 删除所有匹配 <command> 的允许列表条目(下次重启后生效) |
hermes hooks doctor | 对每个已配置的 hook 检查:执行位、允许列表状态、mtime 漂移、JSON 输出有效性和大致执行时间 |
config.yaml 中的 hooks: 块视为特权配置:~/.hermes/agent-hooks/ 内,便于审计路径。hermes hooks doctor,在新添加的 hook 注册前发现它们。hooks: 部分的 PR 时应与审查 CI 配置一样严格。invoke_hook() 分发器。Python 插件先注册(discover_and_load()),shell hook 后注册(register_from_config()),因此在平局情况下 Python pre_tool_call 的 block 决定优先。第一个有效的 block 生效——聚合器在任何回调产生带非空 message 的 {"action": "block", "message": str} 时立即返回。