Hermes 有三套 Hook 系統,對應唔同嘅自動化需求。呢份文檔整理晒所有可用嘅 Hook 事件、配置方法同實用場景。
| 系統 | 註冊方式 | 運行環境 | 主要用途 |
|---|---|---|---|
| Gateway Event Hooks | ~/.hermes/hooks/ 目錄,HOOK.yaml + handler.py |
Gateway only | Logging, alerts, webhooks |
| Shell Hooks | config.yaml 嘅 hooks: 區塊 |
CLI + Gateway | Tool 拦截, auto-formatting, context injection |
| Webhook Subscriptions | hermes webhook subscribe |
Gateway | 外部服務觸發 AI 行跑 |
所有系統都係 non-blocking — hook 出錯會 catch 同 log,永遠唔會 crash agent。
最適合做「開機問候」「摘要卡片」「長任務監控」等場景。
~/.hermes/hooks/
└── my-hook/
├── HOOK.yaml # 宣告聽邊啲 events
└── handler.py # Python handler
name: my-hook
description: 描述呢個 hook 做乜
events:
- agent:start
- agent:end
import json
from datetime import datetime
from pathlib import Path
LOG_FILE = Path.home() / ".hermes" / "hooks" / "my-hook" / "activity.log"
async def handle(event_type: str, context: dict):
"""必須叫 handle。支援 sync 或 async。"""
entry = {
"timestamp": datetime.now().isoformat(),
"event": event_type,
**context,
}
with open(LOG_FILE, "a") as f:
f.write(json.dumps(entry) + "\n")
| Event | 幾時觸發 | Context Keys |
|---|---|---|
gateway:startup |
Gateway 進程啟動 | platforms |
session:start |
新對話建立(第一條訊息) | platform, user_id, session_id, session_key |
session:end |
對話結束(/new 或 /reset 前) |
platform, user_id, session_key |
session:reset |
Session reset 完成 | platform, user_id, session_key |
agent:start |
Agent 開始處理訊息 | platform, user_id, session_id, message |
agent:step |
每次 tool-calling 迭代 | platform, user_id, session_id, iteration, tool_names |
agent:end |
Agent 完成處理 | platform, user_id, session_id, message, response |
command:* |
任何 slash command(wildcard) | platform, user_id, command, args |
Wildcard 匹配: command:* 會 match 所有 command: event(command:model, command:reset 等)。
# ~/.hermes/hooks/long-task-alert/HOOK.yaml
name: long-task-alert
description: Alert when agent is taking many steps
events:
- agent:step
# ~/.hermes/hooks/long-task-alert/handler.py
import os, json, urllib.request
THRESHOLD = 10
TELEGRAM_TOKEN = os.getenv("TELEGRAM_BOT_TOKEN")
CHAT_ID = os.getenv("TELEGRAM_CHAT_ID")
async def handle(event_type: str, context: dict):
iteration = context.get("iteration", 0)
if iteration == THRESHOLD:
msg = f"⚠️ Agent 已經跑咗 {iteration} 步,可能係長任務。"
url = f"https://api.telegram.org/bot{TELEGRAM_TOKEN}/sendMessage"
data = json.dumps({"chat_id": CHAT_ID, "text": msg}).encode()
req = urllib.request.Request(url, data=data, headers={"Content-Type": "application/json"})
urllib.request.urlopen(req)
name: activity-logger
events:
- agent:start
- agent:end
- agent:step
import json
from datetime import datetime
from pathlib import Path
LOG = Path.home() / ".hermes" / "hooks" / "activity-logger" / "activity.jsonl"
async def handle(event_type: str, context: dict):
entry = {"ts": datetime.now().isoformat(), "event": event_type, **context}
with open(LOG, "a") as f:
f.write(json.dumps(entry, default=str) + "\n")
最適合做「權限彈窗」「文件整理」「攔截危險命令」等場景。
喺 config.yaml 加:
hooks:
pre_tool_call:
- command: /path/to/check-risk.py
matcher: "terminal" # regex,只 match terminal tool
timeout: 30
post_tool_call:
- command: /path/to/notify.sh
pre_llm_call:
- command: /path/to/inject-context.py
Stdin(JSON):
{
"hook_event_name": "pre_tool_call",
"tool_name": "terminal",
"tool_input": {"command": "rm -rf /"},
"session_id": "sess_abc123",
"cwd": "/home/user/project",
"extra": {}
}
Stdout(JSON,可選):
// Block 一個 pre_tool_call
{"decision": "block", "reason": "Forbidden command"}
// 或
{"action": "block", "message": "Forbidden command"}
// 為 pre_llm_call 注入 context
{"context": "Today is Friday"}
// 無操作(空或唔 match 嘅 JSON)
| Event | 幾時觸發 | Matcher 支援 | 可以 Block? |
|---|---|---|---|
pre_tool_call |
工具執行前 | ✅ regex match tool name | ✅ 可以 block |
post_tool_call |
工具執行後 | ✅ | ❌ |
transform_terminal_output |
修改 terminal 輸出 | ❌ | ❌ |
transform_tool_result |
修改工具結果 | ❌ | ❌ |
transform_llm_output |
修改 AI 輸出文字 | ❌ | ❌ |
pre_llm_call |
LLM 呼叫前 | ❌ | ❌(注入 context) |
post_llm_call |
LLM 呼叫後 | ❌ | ❌ |
pre_api_request |
API 請求前 | ❌ | ❌ |
post_api_request |
API 請求後 | ❌ | ❌ |
on_session_start |
對話開始 | ❌ | ❌ |
on_session_end |
對話結束 | ❌ | ❌ |
on_session_finalize |
對話最終化 | ❌ | ❌ |
on_session_reset |
Session reset | ❌ | ❌ |
subagent_stop |
Subagent 停止 | ❌ | ❌ |
pre_gateway_dispatch |
Gateway 訊息派發前 | ❌ | ✅ skip/rewrite |
pre_approval_request |
審批請求前 | ❌ | ❌(observer only) |
post_approval_response |
審批回應後 | ❌ | ❌(observer only) |
Matcher 係 regex,只適用於 pre_tool_call / post_tool_call:
hooks:
pre_tool_call:
- command: /path/to/block-dangerous.sh
matcher: "terminal" # 只 match terminal tool
- command: /path/to/check-write.sh
matcher: "write_file|patch" # match write_file 或 patch
- command: /path/to/all-tools.py
# 冇 matcher = match 所有 tools
#!/bin/bash
# ~/.hermes/scripts/notify-approval.sh
# stdin: JSON payload
INPUT=$(cat)
TOOL=$(echo "$INPUT" | python3 -c "import sys,json; print(json.load(sys.stdin).get('tool_name','unknown'))")
osascript -e "display notification \"需要你授權:$TOOL\" with title \"Hermes Hook\" sound name \"Glass\""
# config.yaml
hooks:
pre_approval_request:
- command: ~/.hermes/scripts/notify-approval.sh
#!/usr/bin/env python3
"""Block dangerous terminal commands."""
import sys, json
DANGEROUS = ["rm -rf", "git reset --hard", "git clean -fd", "> /dev/"]
payload = json.load(sys.stdin)
cmd = payload.get("tool_input", {}).get("command", "")
for pattern in DANGEROUS:
if pattern in cmd:
print(json.dumps({"decision": "block", "reason": f"Blocked: contains '{pattern}'"}))
sys.exit(0)
# Allow
sys.exit(0)
hooks:
pre_tool_call:
- command: ~/.hermes/scripts/block-dangerous.py
matcher: "terminal"
timeout: 5
#!/usr/bin/env python3
"""Inject current date into LLM context."""
import sys, json
from datetime import datetime
payload = json.load(sys.stdin)
context = f"Current datetime: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}"
print(json.dumps({"context": context}))
hooks:
pre_llm_call:
- command: ~/.hermes/scripts/inject-datetime.py
最適合做「外部事件觸發 AI 行跑」—— GitHub、Stripe、CI/CD、IoT 等。
Webhook platform 必須先 enable:
# 方法1:Setup wizard
hermes gateway setup
# 方法2:手動 config
# config.yaml
platforms:
webhook:
enabled: true
extra:
host: "0.0.0.0"
port: 8644
secret: "your-hmac-secret"
# 方法3:環境變數
# .env
WEBHOOK_ENABLED=true
WEBHOOK_PORT=8644
WEBHOOK_SECRET=your-secret
# 建立 subscription
hermes webhook subscribe <name> \
--prompt "Prompt template with {payload.fields}" \
--events "event1,event2" \
--description "What this does" \
--skills "skill1,skill2" \
--deliver telegram \
--deliver-chat-id "12345" \
--secret "optional-custom-secret"
# 列出所有 subscriptions
hermes webhook list
# 移除
hermes webhook remove <name>
# 測試
hermes webhook test <name>
hermes webhook test <name> --payload '{"key": "value"}'
用 {dot.notation} 存取 payload 內嘅欄位:
{issue.title} → GitHub issue 標題
{pull_request.user.login} → PR 作者
{data.object.amount} → Stripe 付款金額
{sensor.temperature} → IoT sensor 讀數
hermes webhook subscribe github-issues \
--events "issues" \
--prompt "New GitHub issue #{issue.number}: {issue.title}\n\nAction: {action}\nAuthor: {issue.user.login}\nBody:\n{issue.body}\n\nPlease triage this issue." \
--deliver telegram \
--deliver-chat-id "-100123456789"
hermes webhook subscribe stripe-payments \
--events "payment_intent.succeeded,payment_intent.payment_failed" \
--prompt "Payment {data.object.status}: {data.object.amount} cents from {data.object.receipt_email}" \
--deliver telegram \
--deliver-chat-id "-100123456789"
hermes webhook subscribe ci-builds \
--events "pipeline" \
--prompt "Build {object_attributes.status} on {project.name} branch {object_attributes.ref}\nCommit: {commit.message}" \
--deliver discord \
--deliver-chat-id "1234567890"
hermes webhook subscribe antenna-matches \
--deliver telegram \
--deliver-chat-id "123456789" \
--deliver-only \
--prompt "🎉 New match: {match.user_name} matched with you!" \
--description "Antenna match notifications"
post_tool_call 涉及文件移動/刪除)hooks_auto_accept: true 跳過)~/.hermes/shell-hooks-allowlist.json--deliver-only 可以跳過 LLM(零成本推送到 chat)~/.hermes/webhook_subscriptions.json| 文章講嘅 Hook 玩法 | Hermes 對應系統 | 實現方式 |
|---|---|---|
| 權限彈窗提醒 | Shell Hook | pre_approval_request + osascript/notify-send |
| 開機日程播报 | Gateway Hook | session:start + 天氣 API + 日曆 API |
| 摘要卡片 | Gateway Hook | agent:end + 寫入 markdown 文件 |
| 文件自動整理 | Shell Hook + Skill | post_tool_call + AI 識別 + 移動文件 |
| 久坐提醒 | Cron Job | 定時推送(唔係 Hook 但效果一樣) |
| 長任務完成推送 | Gateway Hook + Bark | agent:end + Bark API |
呢份文檔同 harness-enhancement project 直接相關:
post_tool_call hook 做 post-hoc 驗證pre_tool_call hook 做寫入前檢查pre_llm_call hook 注入 contextFence philosophy 嘅實踐:Hook = 確定性約束,唔靠 LLM 自律,靠代碼強制執行。