事件 Hook 系统
生命周期 hook 让外部脚本无需修改 agent 代码即可响应 agent 和会话事件。当事件触发时,LibreFang 启动配置的可执行文件,通过环境变量传递结构化数据,然后继续运行——无论 hook 是否正常退出。
将每个 hook 放在 ~/.librefang/hooks/ 下的独立子目录中:
~ /.librefang/hooks/
notify-on-end/
HOOK.yaml
run.sh
log-session-reset/
HOOK.yaml
run.py
CopyCopied!
每个子目录必须包含且只包含一个 HOOK.yaml。command 中指定的可执行文件可以是同目录下的任意文件,也可以是系统上的绝对路径——只需确保它是可执行的(chmod +x)。
name : notify-on-agent-end
description : "POST to a webhook when an agent finishes a run"
# Events that trigger this hook. Wildcards are supported.
events :
- agent:end
# Relative path (from this file's directory) or absolute path.
command : ./run.sh
CopyCopied!
字段 是否必填 说明 name是 在日志中显示的人类可读标识符 description否 用于文档目的的自由文本备注 events是 事件名称列表或 glob 模式(如 agent:*、*) command是 事件触发时要调用的可执行文件
事件 触发时机 agent:startAgent 循环开始处理新请求 agent:endAgent 循环完成(成功、错误或达到最大步数) agent:step一次运行中的每个中间推理步骤 session:start在 daemon 生命周期中首次创建或恢复会话 session:end会话被显式关闭 session:reset会话因自动重置策略或手动 API 调用而被清空 gateway:startupLibreFang daemon 完成启动序列
通配符模式:
模式 匹配范围 agent:*agent:start、agent:end、agent:stepsession:*session:start、session:end、session:reset*所有事件
变量 类型 内容 HOOK_EVENTstring 触发的确切事件名称,如 agent:end HOOK_EVENT_DATAJSON string 事件的结构化载荷
agent:start
字段 类型 说明 agent_idstring (UUID) 启动本次运行的 agent session_idstring 与本次运行关联的会话 senderstring 触发本次运行的发送方标识符 message_previewstring 入站消息的前 120 个字符
agent:end
字段 类型 说明 agent_idstring (UUID) 完成本次运行的 agent session_idstring 与本次运行关联的会话 statusstring "ok"、"error" 或 "max_steps"tokens_usedinteger 消耗的总 token 数(prompt + completion) stepsinteger 执行的推理步骤数 duration_msinteger 本次运行的墙钟时间(毫秒) response_previewstring 最终响应的前 120 个字符
agent:step
字段 类型 说明 agent_idstring (UUID) 执行该步骤的 agent session_idstring 本次运行的会话 step_indexinteger 当前运行中从零开始的步骤计数器 tool_callstring | null 正在调用的工具名称(如有)
session:start
字段 类型 说明 agent_idstring (UUID) 所属 agent session_idstring 新启动的会话
session:end
字段 类型 说明 agent_idstring (UUID) 所属 agent session_idstring 已关闭的会话
session:reset
字段 类型 说明 agent_idstring (UUID) 所属 agent session_idstring 被重置的会话 reset_reasonstring "idle"、"daily"、"suspended" 或 "manual"message_count_clearedinteger 已清除的历史消息数量
gateway:startup
字段 类型 说明 versionstring LibreFang daemon 版本 started_atstring (ISO 8601) 启动完成的 UTC 时间戳
一个变换型 hook(其他都是观察型):handler 可以在原始 tool-result 字符串进入对话上下文之前重写它。在 after_tool_call 之后、sanitization 之前触发,所以重写器看到的是原始 tool 输出,runtime 看到的是重写后的。
字段 类型 说明 agent_idstring 跑这个 tool 的 agent tool_namestring 执行的 tool 名 tool_inputobject 传给 tool 的输入 tool_outputstring tool 返回的原始字符串结果
Handler 契约 (HookHandler::transform):
fn transform ( & self , ctx : HookContext ) -> Result < Option < String >, String >;
CopyCopied!
Ok(Some(replacement)) — 替换本回合剩余阶段看到的 tool_output。
Ok(None) — handler 弃权;链上下一个 handler 上。
Err(msg) — warn! 记日志后跳过(fail-open)。其他 handler 仍跑。
HookRegistry::fire_transform() 按注册顺序遍历链,第一个 Ok(Some(_)) 赢 。适合:脱敏(在 LLM 看到前去掉 git diff 输出里的 secret)、摘要(把 5 MB JSON dump 截成 200 行摘要)、测试(给你不想真调的 MCP tool 注入确定性桩)。
任意 agent 结束时发送一条 Slack 兼容的 webhook 通知:
#!/usr/bin/env bash
# ~/.librefang/hooks/notify-on-end/run.sh
set -euo pipefail
# Only act on agent:end
if [ " $HOOK_EVENT " != "agent:end" ]; then
exit 0
fi
AGENT_ID = $( echo " $HOOK_EVENT_DATA " | python3 -c "import sys,json; d=json.load(sys.stdin); print(d['agent_id'])" )
STATUS = $( echo " $HOOK_EVENT_DATA " | python3 -c "import sys,json; d=json.load(sys.stdin); print(d['status'])" )
TOKENS = $( echo " $HOOK_EVENT_DATA " | python3 -c "import sys,json; d=json.load(sys.stdin); print(d['tokens_used'])" )
WEBHOOK_URL = "${ SLACK_WEBHOOK_URL :- }"
if [ -z " $WEBHOOK_URL " ]; then
echo "SLACK_WEBHOOK_URL not set, skipping" >&2
exit 0
fi
curl -s -X POST " $WEBHOOK_URL " \
-H "Content-Type: application/json" \
-d "{ \" text \" : \" Agent \` ${ AGENT_ID } \` finished with status *${ STATUS }* (${ TOKENS } tokens) \" }"
CopyCopied!
让 daemon 能执行它:
chmod +x ~/.librefang/hooks/notify-on-end/run.sh
CopyCopied!
daemon 在启动时以及收到 SIGHUP 后会扫描 hooks 目录。运行中的 daemon 不会在执行过程中热重载 hook 文件——新增 hook 后请重启或发送 SIGHUP。
非零退出码:记录 WARN 并继续
5 秒墙钟超时;超时后将被终止
可执行文件缺失:记录为 WARN,其他 hook 仍正常触发
hook 内部无法调用 LLM
无返回值——hook 不能修改 agent 的响应
无密钥注入
每个 HOOK.yaml 只能指定一个可执行文件