钩子 — 基础
生命周期点、组合、钩子上下文和本地测试。所有钩子的列表见 06-hooks-reference。
6.1 生命周期点
| 点 | 签名 | 作用 |
|---|---|---|
before_user_message |
(ctx, msg) |
注入、重写、丢弃消息或自行回复 |
before_tool_call |
(ctx, tool, args) |
重写参数、拒绝(deny)或在工具调用前替换结果(result) |
after_tool_result |
(ctx, tool, result) |
重写工具结果(编辑、success、data、visual) |
before_inference |
(ctx, request) |
模型路由、采样参数、重新打包提示词/工具 |
after_turn |
(ctx, question, answer) |
在最终回答之后;可以发出回合后通知 |
after_assistant_message |
(ctx, answer) |
在保存前重写/抑制最终回答(output-guardrail) |
on_token |
(ctx, token, final_answer) |
拦截流式 token:替换或抑制(实时审核/编辑;output-only) |
before_context_evict |
(ctx, evicted, summary) |
自定义压缩:替换被挤出窗口消息的 summary |
after_loop |
(ctx, reason) |
在运行循环停止时(job_done、terminated、blocked、budget_exhausted、max_turns、……) |
on_spawn |
(ctx, child_pid) |
在进程创建之后(进程自身的钩子) |
on_terminate |
(ctx, exit_code) |
在进程终止时(在唤醒等待者之前) |
on_error |
(ctx, message) |
在进程发生内核错误时(例如不可恢复的推理故障) |
on_session_created |
(ctx, session_id) |
在进程会话注册之后 |
on_session_closed |
(ctx, session_id) |
在会话关闭/过期时(在删除记录之前) |
before_retry |
(ctx, attempt, error) |
重试决策:retry=false 禁止,delay_ms — backoff |
before_syscall |
(ctx, syscall) |
拦截 syscall(包括 IPC Send/Recv):deny 并附带原因 |
on_budget_threshold |
(ctx, level, spent, max, ratio) |
预算越过 warn_at(或耗尽)— 主动升级 |
结果 before_tool_call
所有字段均为可选;钩子按注册顺序执行,每个钩子都能看到 前一个钩子留下的参数:
| 字段 | 效果 |
|---|---|
arguments |
替换调用参数 |
deny |
以原因拒绝调用 → failed-结果,处理程序不会启动 |
result |
完全替换结果(缓存/模拟/预计算响应),处理程序不会启动 |
notice |
流式传输到聊天 UI 的文本;不会进入 LLM 上下文 |
result 优先于 deny。
结果 after_tool_result
| 字段 | 效果 |
|---|---|
content |
替换给 LLM 的文本 |
success |
覆盖成功标志 |
data |
替换结构化数据 |
visual |
替换 UI 块 |
notice |
向聊天 UI 发送通知 |
结果 before_inference
钩子接收完全组装好的请求
(request:{ model, messages, tools, max_tokens, temperature }),并且可以
返回 model / temperature / max_tokens / messages /
tools 中的任意字段进行覆盖。在回合循环中的每次推理调用时都会调用
(包括重试),因此条件必须是幂等的。对于脚本/钩子的 llm(...)-
辅助函数不会调用(以免调用 llm 的钩子陷入循环)。
此外还可以控制调用结果:
| 字段 | 效果 |
|---|---|
deny |
以原因拒绝推理 → 进程被阻塞(Blocked) |
response |
合成助手响应 — 提供方不会被调用 |
结果 before_retry
在瞬时推理错误准备好重试时调用(无论是在打开 提供方时,还是在流中途)。所有字段均为可选:
| 字段 | 效果 |
|---|---|
retry |
false — 禁止重试(转到下一个提供方) |
delay_ms |
覆盖 backoff(毫秒) |
结果 before_syscall
在 handle_syscall 中、能力检查之前调用 — 统一的拦截点,
包括代理间 Send/Recv:
| 字段 | 效果 |
|---|---|
deny |
以原因拒绝 syscall(AccessDenied) |
on_budget_threshold
通知(无返回值):预算达到 warn_at 或已耗尽。
每个层级触发一次。level — session | group | user,
spent、max、ratio。对于会话在 budget: { warn_at: 0.8 }
中设置
代理;对于组和 per-user — 在 config/budgets.yaml 中(参见
03-server-configuration)。
结果 on_token
对每个流式传输给客户端的块调用(final_answer: true — 助手
回答的 token,false — 服务/状态)。所有字段均为可选:
| 字段 | 效果 |
|---|---|
replace |
替换 token 文本 |
suppress |
不向客户端发送 token |
钩子只影响发往客户端的流:原始文本仍然会进入 上下文和转录。
结果 before_context_evict
当上下文窗口溢出时,内核会挤出旧消息。钩子
接收被挤出的消息(evicted:{ role, content } 列表)和当前
summary,并且可以返回自己的:
| 字段 | 效果 |
|---|---|
summary |
替换被挤出上下文的 summary(自定义摘要) |
干预追踪
每个钩子决策都会作为 hook_intercepted 事件写入 logs/events.db 和
per-process JSONL(logs/process-*.jsonl)— 用于审计和 SQL 查询。
各点及其 action:
| 点 | action |
|---|---|
before_tool_call |
rewrite_args / deny / short_circuit |
after_tool_result |
transform_result |
before_inference |
rewrite_request |
on_token |
replace / suppress |
before_context_evict |
compact |
before_user_message |
reply / suppress / transform / inject / notice |
after_assistant_message |
replace / suppress / notice |
after_turn |
notice |
after_loop |
after_loop(带 reason) |
before_tool_call / after_tool_result / before_inference 的决策还会
以 kind hook(logs/messages.db)行进入会话转录,并带有
元数据 { point, hook, action, … } — 可通过 trace_tool 和 UI 看到。
中立的(不做任何更改的)钩子不会产生事件。
记录的是钩子做了什么,而不是原始数据,因此编辑不会记录它隐藏的内容。
结果 before_user_message
钩子可以返回包含任意字段的映射(所有字段均为可选):
| 字段 | 效果 |
|---|---|
notice |
流式输出到聊天 UI 的文本;不会进入 LLM 上下文 |
reply |
完整回复——跳过 LLM 回合 |
suppress |
完全丢弃用户消息 |
inject |
在用户消息之前插入额外消息 |
replace_message |
替换用户消息 |
钩子出错永远不会破坏回合——异常会被记录,钩子返回中立结果。
结果 after_assistant_message
输出护栏:在最终回复写入上下文和转录之前调用。所有字段均为可选:
| 字段 | 效果 |
|---|---|
replace |
整体替换助手消息 |
suppress |
不保存回复(不向上下文/转录写入任何内容) |
notice |
在聊天 UI 中通知 |
rework |
拒绝回复并继续回合:内核(可选)清空流(replace)并注入 correction 消息,模型重新回答。每回合不超过 MAX_REWORK_ROUNDS(2)次。清空由 Rework::clear_stream 控制(默认 true) |
钩子也可以直接就地修改 answer(Rust)——修改会被检测到。
如果回复被修改或抑制,内核会将修正后的回复作为
final_answer 令牌重新发出(带 replace: true),以便客户端显示它而不是流式输出的原始内容。rework 不保存被拒绝的回复,也不调用
after_turn——控制权返回循环,进入新一轮推理。
流水线。 在第一个终止性决策(rework/suppress)时,其余
输出钩子不会被调用:胜出者由 priority 决定(相同时按
注册顺序)。同样的钩子也适用于通过
job_done 完成的场景:如果护栏拒绝
completion,内核不会终止进程,而是将修正结果作为 job_done 的结果返回给模型(veto)——每回合不超过 MAX_REWORK_ROUNDS(2)次。
隔离。 每次钩子调用都包裹了防 panic 保护:发生 panic 的钩子
会被跳过并返回中立结果,不会使回合崩溃。调用还受
时间限制——在智能体的 YAML 中设置 timeouts.hook_secs(覆盖
内核的全局默认值,默认 300 秒)。超时后钩子被跳过并返回
中立结果;日志中会写入警告。
指标。 每次钩子调用都会被内核计入指标 (hook, point):
调用次数、panic 次数、超时次数、总时长和平均时长、最后一次
错误。管理员可以获取快照:GET /v1/hooks/metrics。
6.23 将钩子连接到智能体
# config/agents/<agent>/agent.yaml
hook_files:
- hooks/faq_cache.yaml
- hooks/memory.yaml
在运行期间,可以通过 Kernel API 为进程连接/断开钩子——
与工具对等:grant_hook(pid, name) / revoke_hook(pid, name) /
process_hooks(pid)(参见 grant_tool/revoke_tool/process_tools)。
已连接的钩子从下一次调用起生效。
工作流中的 tool 步骤经过相同的 before_tool_call /
after_tool_result 启动进程钩子(Kernel::run_tool_for);进程的 allowlist
不适用于它们——步骤的组成由工作流作者指定。
6.24 组合与全局钩子
任何 YAML 钩子都接受通用组合键:
priority: 10 # выше — раньше; при равенстве сохраняется порядок регистрации
enabled: true # false = хук не вызывается
global: true # применять ко ВСЕМ процессам, а не только тем, что ссылаются
when: # условия активации (все заполненные поля должны совпасть)
templates: [ngu-agent]
agents: [ngu-1] # имена процессов
users: [alice]
sessions: [sess-42]
- 列表是备选项(OR);不同字段是 AND。空字段 = 无限制。
- 连接到智能体的钩子(
hooks:)仍会按when/enabled过滤。 - 全局钩子:将 YAML 放入
config/hooks/*.yaml——服务器会在启动时 加载它们并标记为global: true(对所有智能体统一进行 DLP/审计)。
基于数据的组合(bag)
钩子可以通过 per-turn 袋子
(HookBag)相互传递命名值:一个发布键,另一个将其作为输入端口读取。这不
依赖于 priority,也不需要重写用户消息。
inputs:
query: { from: retrieval.query, default: "", required: false } # читать ключ мешка
outputs:
- retrieval.query # публиковать ключ (или объект с key/doc)
- 对于内置钩子,端口在代码中定义并自动绑定:例如,
condense发布retrieval.query,而rag读取它(否则取原始 消息)。智能体作者只需列出钩子即可。 - 输入解析顺序:袋子中的值 → 字面量
default→ 钩子的内置 fallback。 - 自动排序:生产者始终在消费者之前;
priority——平局决胜, 出现循环时——回退到priority。 - 袋子按进程作用域,并在新的用户消息时清空。
- 脚本钩子读取
ctx.inputs并返回outputs({ "<ключ>": value })。 - 模式与内省:
GET /v1/hooks/bag(按键的 producers/consumers)。
标准键:retrieval.query、retrieval.source、intent.label、
lang.user、model.route(agent_os_core::bag_keys)。自定义键默认
命名空间为 <hook>.<port>。
6.25 钩子上下文
HookContext 包含:pid、agent、template、session_id、user_id、
config,以及:
parent和depth——进程树中的父级和深度(子智能体);tools——进程的工具 allowlist;budget_total/budget_spent和方法budget_remaining();inputs——组合允许的输入端口({ port: value });bag—— 用于发布值的 per-turn 袋子(见 §6.24)。
6.26 本地测试钩子
agent-os hook <hook.yaml> info # показать распарсенный конфиг
agent-os hook <hook.yaml> user "Привет, мир" # запустить before_user_message
agent-os hook <hook.yaml> turn "question" "answer" # запустить after_turn
agent-os hook <hook.yaml> loop "job_done" # запустить after_loop
agent-os hook <hook.yaml> tool echo '{"msg":"x"}' # запустить before_tool_call
agent-os hook <hook.yaml> result echo "text" false # запустить after_tool_result
agent-os hook <hook.yaml> inference '{"model":"m"}'
agent-os hook <hook.yaml> token "chunk" [true|false] # запустить on_token
agent-os hook <hook.yaml> evict "summary" # запустить before_context_evict
agent-os hook <hook.yaml> spawn # запустить on_spawn
agent-os hook <hook.yaml> terminate 0 # запустить on_terminate
agent-os hook <hook.yaml> error "boom" # запустить on_error
# флаги
--db <path> # перекрыть db_path (для тестов — временный файл)
--live # судья/запоминатель — реальный дочерний процесс (нужен ключ API)
--agent <name> # имя процесса в контексте хука
--template <name> # имя шаблона
--user <id> # user_id
--session <id> # session_id