OpenClaw Hook系统:Agent框架的非侵入式扩展机制

开发复杂AI Agent系统时,经常遇到一个痛点:想在模型调用前增加日志、消息处理前后插入业务规则,就要侵入主流程代码。修改一处逻辑,就要改动核心链路,很容易出现牵一发而动全身的耦合问题。

我们希望核心Agent推理逻辑保持稳定,日志、RAG知识注入、安全过滤、多通道适配这些附加能力,都可以作为插件外挂,不需要改动框架源码,就能灵活增删业务逻辑

Web开发领域,Gin的中间件通过洋葱模型,在HTTP请求生命周期实现非侵入扩展;而OpenClaw的Hook系统,把这套思想迁移到AI Agent的完整消息生命周期,并且做了进一步增强,不只是简单的洋葱链式调用,提供了多套执行模式,适配Agent各式各样的业务场景。

什么是Hook系统

Hook本质就是框架预先在业务流程的关键节点预埋好的"检查站"。 框架主流程只负责定义什么时候触发钩子,不关心钩子内部做什么业务;外部插件只需要向框架注册对应点位的处理函数,当程序运行到该节点,框架自动调度所有注册的插件逻辑。

核心价值:业务逻辑与Agent核心推理流程解耦。 新增审计、RAG增强、敏感词拦截、多消息通道,全部以插件形式注册Hook,主流程代码一行不用改。

OpenClaw将Agent从接收用户消息,到模型推理、工具调用、子Agent执行、消息输出落库,完整生命周期拆解出25个Hook点位,覆盖网关会话、消息入站、推理循环、消息出站、会话压缩、子Agent调度全链路。

分层 典型Hook点位
网关会话层 gateway_startsession_startsession_end
消息入站层 inbound_claimmessage_received
推理循环层 before_model_resolvebefore_prompt_buildllm_inputllm_outputbefore_tool_callagent_end
消息出站层 before_message_writemessage_sendingmessage_sent
系统辅助层 会话压缩、子Agent生命周期相关钩子

不同Hook点位职责边界清晰:

  • before_prompt_build:Prompt组装之前,适合注入RAG检索的外部知识;
  • llm_input:Prompt已经拼接完成,即将发给大模型,适合做输入安全扫描、Prompt注入防御。

同样是修改上下文,介入时机不一样,分工明确,插件开发者可以在正确的节点做正确的事。

四种Hook执行模式,不止简单链式调用

Gin中间件基本是统一的洋葱串行模型,但是Agent业务场景复杂:有的逻辑只需要打日志,不需要阻塞主流程;有的需要串行修改数据;有的需要抢占消息处理权;热路径场景绝对不能允许异步延迟。

OpenClaw设计4种执行模式,每个Hook点位绑定固定模式,框架内部Dispatch调度入口自动匹配执行策略。

1. Void 模式|并行旁观者

  • 行为:所有注册的Handler并行执行,不关心返回结果,单个插件报错不会阻断主流程。

  • 适用:日志埋点、审计统计、事件通知。

  • 代表点位:llm_output、message_received、message_sent

    类比:路边监控摄像头,只记录事件,不干预车辆通行。

2. Modifying 模式|链式加工厂

  • 行为:按优先级串行执行,前一个插件输出结果,作为下一个插件输入,支持自定义结果合并策略。

  • 适用:Prompt增强、消息改写、动态切换模型配置。

  • 代表点位:before_prompt_build、before_model_resolve

    类比流水线,上一道工序产出交给下一道工序继续加工。 内置两种合并策略:MergeFirstDefined高优插件优先覆盖;MergeAppend把多个插件的上下文字符串拼接在一起,RAG场景非常实用。

3. Claiming 模式|竞争认领

  • 行为:按优先级依次执行,只要某一个插件返回Handled=true,直接返回结果,后续插件全部跳过。

  • 适用:多消息通道分发。

  • 代表点位:inbound_claim

    类比出租车抢单,抢到订单的司机接管,其余司机直接退出。

4. Sync 模式|严苛热路径

  • 行为:纯同步串行,禁止任何异步操作,追求极致低延迟。

  • 适用:消息落库前拦截、内存状态同步,高性能热路径。

  • 代表点位:before_message_write、tool_result_persist

    类比百米计时器,冲线瞬间立刻返回结果,不能等待IO。

优先级机制

所有Hook注册时可以指定priority,数值越大优先级越高,优先执行。 Modifying、Claiming、Sync模式严格按照优先级排序;Void模式仅影响goroutine启动顺序,不保证执行时序。

和Gin中间件对比

  1. Gin中间件 :统一洋葱模型,串行流转,依靠c.Next()c.Abort()控制流程;适合HTTP请求响应链路;全部是串行,没有并行事件、抢占认领、纯同步热路径区分。
  2. OpenClaw Hook :针对Agent生命周期,划分25个细粒度点位,四种调度模式;既支持链式修改,也支持并行事件通知、抢占认领、零异步开销热路径;配套完整结果合并策略,适配大模型RAG、多通道、工具调用、子Agent等AI特有场景。

二者底层思想同源:事件预埋 + 外部注册 + 非侵入扩展,但Hook系统针对Agent领域做了专门的架构增强。

Go核心Demo实现

talk is cheap,show me the code 完整代码包含Hook枚举、四种模式实现、合并策略、统一调度入口,并且模拟完整消息生命周期执行。

go 复制代码
// OpenClaw Hook System --- Go 版核心实现 Demo
//
// 展示 OpenClaw 的"非侵入式逻辑注入机制":
//   一个注册表 + 四种调度策略(Void / Modifying / Claiming / Sync)
//
// 运行: go run demo/hook_system.go
package main
import (
"context"
"fmt"
"log"
"sort"
"strings"
"sync"
"time"
)

// =====================================================================
// 1. Hook 名称枚举 --- Agent 生命周期中的 25 个"检查站"
// =====================================================================
type HookName string
const (
// 网关会话层
GatewayStart HookName = "gateway_start"
SessionStart HookName = "session_start"
SessionEnd   HookName = "session_end"
GatewayStop  HookName = "gateway_stop"
// 消息入站层
InboundClaim    HookName = "inbound_claim"
MessageReceived HookName = "message_received"
// 推理循环层(核心)
BeforeModelResolve HookName = "before_model_resolve"
BeforePromptBuild  HookName = "before_prompt_build"
LLMInput           HookName = "llm_input"
LLMOutput          HookName = "llm_output"
BeforeToolCall     HookName = "before_tool_call"
AfterToolCall      HookName = "after_tool_call"
ToolResultPersist  HookName = "tool_result_persist"
AgentEnd           HookName = "agent_end"
// 消息出站层
BeforeMessageWrite HookName = "before_message_write"
MessageSending     HookName = "message_sending"
MessageSent        HookName = "message_sent"
// 系统辅助层
BeforeCompaction HookName = "before_compaction"
AfterCompaction  HookName = "after_compaction"
SubagentSpawned  HookName = "subagent_spawned"
SubagentEnded    HookName = "subagent_ended"
)

// =====================================================================
// 2. 四种执行模式
// =====================================================================
type HookMode int
const (
ModeVoid      HookMode = iota // 并行旁观者:goroutine 并行,不关心返回值
ModeModifying                 // 链式加工厂:按优先级串行,前一个结果喂给后一个
ModeClaiming                  // 先到先得:第一个 handled=true 的直接返回
ModeSync                      // 同步热路径:禁止异步,纯同步执行
)

var hookModeMap = map[HookName]HookMode{
// Void 模式
LLMInput: ModeVoid, LLMOutput: ModeVoid,
AgentEnd: ModeVoid, MessageReceived: ModeVoid,
MessageSent: ModeVoid, AfterToolCall: ModeVoid,
SessionStart: ModeVoid, SessionEnd: ModeVoid,
GatewayStart: ModeVoid, GatewayStop: ModeVoid,
BeforeCompaction: ModeVoid, AfterCompaction: ModeVoid,
SubagentSpawned: ModeVoid, SubagentEnded: ModeVoid,
// Modifying 模式
BeforeModelResolve: ModeModifying,
BeforePromptBuild:  ModeModifying,
BeforeToolCall:     ModeModifying,
MessageSending:     ModeModifying,
// Claiming 模式
InboundClaim: ModeClaiming,
// Sync 模式
ToolResultPersist:  ModeSync,
BeforeMessageWrite: ModeSync,
}

// =====================================================================
// 3. 事件与结果类型
// =====================================================================
type HookEvent map[string]any
type HookResult map[string]any
type ClaimResult struct {
Handled bool
Data    any
}
type SyncResult struct {
Block   bool
Message any
}

// =====================================================================
// 4. Hook 注册记录
// =====================================================================
type HookRegistration struct {
PluginID  string
HookName  HookName
Priority  int // 数值越大,优先级越高,越先执行
TimeoutMs int // 单个 handler 超时(毫秒),0 表示不限
// 按模式选择对应的 handler 签名
AsyncHandler func(ctx context.Context, event HookEvent) (HookResult, error)
ClaimHandler func(ctx context.Context, event HookEvent) (*ClaimResult, error)
SyncHandler  func(event HookEvent) (*SyncResult, error)
}

// =====================================================================
// 5. 合并策略 --- 当多个插件注册了同一个 Modifying Hook,如何合并结果
// =====================================================================
type MergeFunc func(accumulated, incoming HookResult) HookResult

// 先到先得:高优先级插件的字段一旦有值,后续插件不覆盖
func MergeFirstDefined(acc, next HookResult) HookResult {
if acc == nil {
return next
	}
for k, v := range next {
if _, exists := acc[k]; !exists {
acc[k] = v
		}
	}
return acc
}

// 拼接累加:字符串字段拼接,适用于 context 注入类 Hook
func MergeAppend(acc, next HookResult) HookResult {
if acc == nil {
return next
	}
for k, v := range next {
if existing, ok := acc[k]; ok {
if es, ok1 := existing.(string); ok1 {
if ns, ok2 := v.(string); ok2 {
acc[k] = es + "\n" + ns
continue
				}
			}
		} else {
acc[k] = v
		}
	}
return acc
}

var defaultMergeStrategy = map[HookName]MergeFunc{
BeforeModelResolve: MergeFirstDefined, // model/provider override: 高优先级 hook 胜出
BeforePromptBuild:  MergeAppend,       // context 注入: 所有插件的知识拼接在一起
BeforeToolCall:     MergeFirstDefined,
MessageSending:     MergeFirstDefined,
}

// =====================================================================
// 6. HookRunner --- 核心引擎
// =====================================================================
type HookRunner struct {
mu    sync.RWMutex
hooks []HookRegistration
}

func NewHookRunner() *HookRunner {
return &HookRunner{}
}

// On 是插件注册 Hook 的唯一入口
func (r *HookRunner) On(reg HookRegistration) {
r.mu.Lock()
defer r.mu.Unlock()
r.hooks = append(r.hooks, reg)
}

// 按 hookName 过滤 + 按 priority 降序排序(高优先级先执行)
func (r *HookRunner) getHooksForName(name HookName) []HookRegistration {
r.mu.RLock()
defer r.mu.RUnlock()
var matched []HookRegistration
for _, h := range r.hooks {
if h.HookName == name {
matched = append(matched, h)
		}
	}
sort.Slice(matched, func(i, j int) bool {
return matched[i].Priority > matched[j].Priority
	})
return matched
}

func withTimeout(ctx context.Context, timeoutMs int, fn func(context.Context) error) error {
if timeoutMs <= 0 {
return fn(ctx)
	}
ctx, cancel := context.WithTimeout(ctx, time.Duration(timeoutMs)*time.Millisecond)
defer cancel()
return fn(ctx)
}

// ----- Mode 1: Void --- 并行旁观者 -----
func (r *HookRunner) RunVoidHook(ctx context.Context, name HookName, event HookEvent) {
hooks := r.getHooksForName(name)
if len(hooks) == 0 {
return
	}
var wg sync.WaitGroup
for _, h := range hooks {
wg.Add(1)
go func(hook HookRegistration) {
defer wg.Done()
err := withTimeout(ctx, hook.TimeoutMs, func(c context.Context) error {
_, err := hook.AsyncHandler(c, event)
return err
			})
if err != nil {
log.Printf("[hook:void] %s plugin=%s error: %v", name, hook.PluginID, err)
			}
		}(h)
	}
wg.Wait()
}

// ----- Mode 2: Modifying --- 链式加工厂 -----
func (r *HookRunner) RunModifyingHook(
ctx context.Context,
name HookName,
event HookEvent,
shouldStop func(HookResult) bool,
) (HookResult, error) {
hooks := r.getHooksForName(name)
if len(hooks) == 0 {
return nil, nil
	}
merge := defaultMergeStrategy[name]
if merge == nil {
merge = MergeFirstDefined
	}
var accumulated HookResult
for _, h := range hooks {
var result HookResult
err := withTimeout(ctx, h.TimeoutMs, func(c context.Context) error {
r, e := h.AsyncHandler(c, event)
result = r
return e
		})
if err != nil {
log.Printf("[hook:modifying] %s plugin=%s error: %v", name, h.PluginID, err)
continue
		}
if result != nil {
accumulated = merge(accumulated, result)
if shouldStop != nil && shouldStop(accumulated) {
break
			}
		}
	}
return accumulated, nil
}

// ----- Mode 3: Claiming --- 先到先得 -----
func (r *HookRunner) RunClaimingHook(ctx context.Context, name HookName, event HookEvent) (*ClaimResult, error) {
hooks := r.getHooksForName(name)
if len(hooks) == 0 {
return nil, nil
	}
for _, h := range hooks {
var result *ClaimResult
err := withTimeout(ctx, h.TimeoutMs, func(c context.Context) error {
r, e := h.ClaimHandler(c, event)
result = r
return e
		})
if err != nil {
log.Printf("[hook:claiming] %s plugin=%s error: %v", name, h.PluginID, err)
continue
		}
if result != nil && result.Handled {
return result, nil
		}
	}
return nil, nil
}

// ----- Mode 4: Sync --- 同步热路径 -----
func (r *HookRunner) RunSyncHook(name HookName, event HookEvent) *SyncResult {
hooks := r.getHooksForName(name)
if len(hooks) == 0 {
return nil
	}
current := event["message"]
for _, h := range hooks {
result, err := h.SyncHandler(HookEvent{"message": current})
if err != nil {
log.Printf("[hook:sync] %s plugin=%s error: %v", name, h.PluginID, err)
continue
		}
if result == nil {
continue
		}
if result.Block {
return &SyncResult{Block: true}
		}
if result.Message != nil {
current = result.Message
		}
	}
return &SyncResult{Message: current}
}

// Dispatch 统一调度入口,自动匹配执行模式
func (r *HookRunner) Dispatch(ctx context.Context, name HookName, event HookEvent) (any, error) {
mode := hookModeMap[name]
switch mode {
case ModeVoid:
r.RunVoidHook(ctx, name, event)
return nil, nil
case ModeModifying:
return r.RunModifyingHook(ctx, name, event, nil)
case ModeClaiming:
return r.RunClaimingHook(ctx, name, event)
case ModeSync:
return r.RunSyncHook(name, event), nil
default:
return nil, fmt.Errorf("unknown hook mode for %s", name)
	}
}

// =====================================================================
// main --- 模拟完整消息处理生命周期
// =====================================================================
func main() {
runner := NewHookRunner()
// ==================== 注册插件 ====================
// 插件 A:RAG 知识增强(Modifying 模式,before_prompt_build)
runner.On(HookRegistration{
PluginID: "rag-plugin",
HookName: BeforePromptBuild,
Priority: 100,
AsyncHandler: func(ctx context.Context, event HookEvent) (HookResult, error) {
docs := "相关文档:OpenClaw 使用 Hook 系统实现非侵入式逻辑注入..."
return HookResult{"appendContext": docs}, nil
		},
	})
// 插件 B:补充企业知识库(Modifying 模式,同一个 Hook,低优先级)
runner.On(HookRegistration{
PluginID: "enterprise-kb",
HookName: BeforePromptBuild,
Priority: 50,
AsyncHandler: func(ctx context.Context, event HookEvent) (HookResult, error) {
return HookResult{"appendContext": "企业规范:所有回答需符合合规要求。"}, nil
		},
	})
// 插件 C:Token 用量监控(Void 模式,llm_output)
runner.On(HookRegistration{
PluginID:  "metrics-plugin",
HookName:  LLMOutput,
Priority:  50,
TimeoutMs: 5000,
AsyncHandler: func(ctx context.Context, event HookEvent) (HookResult, error) {
fmt.Printf("  📊 [metrics] model=%v, tokens=%v\n", event["model"], event["tokens"])
return nil, nil
		},
	})
// 插件 D:审计日志(Void 模式,同一个 Hook,并行执行)
runner.On(HookRegistration{
PluginID: "audit-plugin",
HookName: LLMOutput,
Priority: 30,
AsyncHandler: func(ctx context.Context, event HookEvent) (HookResult, error) {
fmt.Printf("  📝 [audit] 已记录本次 LLM 调用到审计日志\n")
return nil, nil
		},
	})
// 插件 E:Telegram 通道认领(Claiming 模式,inbound_claim)
runner.On(HookRegistration{
PluginID: "telegram-plugin",
HookName: InboundClaim,
Priority: 200,
ClaimHandler: func(ctx context.Context, event HookEvent) (*ClaimResult, error) {
if source, _ := event["source"].(string); source == "telegram" {
return &ClaimResult{Handled: true, Data: "telegram-channel-1"}, nil
			}
return &ClaimResult{Handled: false}, nil
		},
	})
// 插件 F:Discord 通道认领(Claiming 模式,低优先级)
runner.On(HookRegistration{
PluginID: "discord-plugin",
HookName: InboundClaim,
Priority: 100,
ClaimHandler: func(ctx context.Context, event HookEvent) (*ClaimResult, error) {
if source, _ := event["source"].(string); source == "discord" {
return &ClaimResult{Handled: true, Data: "discord-channel-1"}, nil
			}
return &ClaimResult{Handled: false}, nil
		},
	})
// 插件 G:敏感词过滤(Sync 模式,before_message_write)
runner.On(HookRegistration{
PluginID: "security-plugin",
HookName: BeforeMessageWrite,
Priority: 300,
SyncHandler: func(event HookEvent) (*SyncResult, error) {
msg, _ := event["message"].(string)
if strings.Contains(msg, "机密") {
return &SyncResult{Block: true}, nil
			}
cleaned := strings.ReplaceAll(msg, "敏感词", "***")
return &SyncResult{Message: cleaned}, nil
		},
	})
// 插件 H:消息格式化(Sync 模式,同一个 Hook,低优先级,链式传递)
runner.On(HookRegistration{
PluginID: "formatter-plugin",
HookName: BeforeMessageWrite,
Priority: 100,
SyncHandler: func(event HookEvent) (*SyncResult, error) {
msg, _ := event["message"].(string)
return &SyncResult{Message: msg + " (已格式化)"}, nil
		},
	})

// ==================== 模拟完整消息处理流程 ====================
ctx := context.Background()
fmt.Println("╔══════════════════════════════════════════════════════╗")
fmt.Println("║     OpenClaw Hook System Demo --- 消息处理全流程       ║")
fmt.Println("╚══════════════════════════════════════════════════════╝")

// Step 1: inbound_claim --- Claiming 模式
fmt.Println("\n🔹 Step 1: inbound_claim --- 谁来接这单?")
claim, _ := runner.Dispatch(ctx, InboundClaim, HookEvent{"source": "telegram"})
if c, ok := claim.(*ClaimResult); ok && c != nil {
fmt.Printf("  ✅ 认领结果: handled=%v, channel=%v\n", c.Handled, c.Data)
	}

// Step 2: message_received --- Void 模式
fmt.Println("\n🔹 Step 2: message_received --- 通知消息已入库")
runner.Dispatch(ctx, MessageReceived, HookEvent{"content": "用户问:什么是 Hook?"})
fmt.Println("  ✅ 已并行通知所有监听插件(无注册则跳过)")

// Step 3: before_prompt_build --- Modifying 模式(拼接累加)
fmt.Println("\n🔹 Step 3: before_prompt_build --- 注入 RAG 知识")
promptResult, _ := runner.Dispatch(ctx, BeforePromptBuild, HookEvent{"messages": "用户问题"})
if pr, ok := promptResult.(HookResult); ok {
fmt.Printf("  ✅ 合并后的 context:\n")
if ac, ok := pr["appendContext"].(string); ok {
for _, line := range strings.Split(ac, "\n") {
fmt.Printf("     │ %s\n", line)
			}
		}
	}

// Step 4: llm_output --- Void 模式(metrics + audit 并行)
fmt.Println("\n🔹 Step 4: llm_output --- 模型输出后并行通知")
runner.Dispatch(ctx, LLMOutput, HookEvent{"model": "gpt-5.5", "tokens": 1234})

// Step 5: before_message_write --- Sync 模式(链式 + 一票否决)
fmt.Println("\n🔹 Step 5a: before_message_write --- 正常消息")
syncResult := runner.RunSyncHook(BeforeMessageWrite, HookEvent{
"message": "这是一段含有敏感词的回复内容",
	})
fmt.Printf("  ✅ block=%v, message=%v\n", syncResult.Block, syncResult.Message)

fmt.Println("\n🔹 Step 5b: before_message_write --- 触发一票否决")
syncResult2 := runner.RunSyncHook(BeforeMessageWrite, HookEvent{
"message": "这是机密文件的内容",
	})
fmt.Printf("  🚫 block=%v(消息被拦截,不会写入)\n", syncResult2.Block)

// Step 6: message_sent --- Void 模式
fmt.Println("\n🔹 Step 6: message_sent --- 任务完成")
runner.Dispatch(ctx, MessageSent, HookEvent{"status": "delivered"})
fmt.Println("  ✅ 生命周期结束")

fmt.Println("\n══════════════════════════════════════════════════════")
fmt.Println("Demo 完成。核心架构:一个注册表 + 四种调度策略")
fmt.Println("插件只需调用 runner.On(),核心框架完全不需要修改。")
}

总结

OpenClaw Hook系统,借鉴了Gin中间件非侵入扩展的设计思想,面向AI Agent场景做了深度定制。

  • 将Agent完整生命周期拆解25个细粒度Hook点位,把黑盒的推理流程变成可观测、可干预的透明链路;
  • 设计Void、Modifying、Claiming、Sync四种执行模式,覆盖事件通知、数据改写、抢占分发、高性能热路径;
  • 配套优先级、多种结果合并策略,多个插件共存时行为可预期。

这套架构最大的收益:核心Agent推理逻辑稳定不动,RAG、安全过滤、审计埋点、多平台通道、子Agent能力全部以插件形式挂载。新增、删除、修改业务插件,不需要改动框架主流程代码,彻底解决Agent系统业务膨胀带来的耦合灾难。

相关推荐
LiaCode15 分钟前
Redis 接入 AI 学习总结:从向量检索到 Agent 上下文引擎
后端
步行cgn16 分钟前
Spring Boot 保证版本一致性的核心机制
后端
步行cgn19 分钟前
Spring Boot 启动器(Starter)详解:依赖组合的标准模式
后端
用户78136671144521 分钟前
C++ 锁管理器 与 智能指针
后端
暧暧内含光25 分钟前
IPC 和 RPC,有什么区别?
后端
YIAN26 分钟前
从 Docker 容器操作到 TS 高级类型:前端开发者必备的两套核心工具全解
后端·docker
一开28 分钟前
一个自己开发的 Agent Harness-沙箱篇
后端
yangdaxiageo34 分钟前
白帽GEO的结构化信任工程:杨大侠GEO商业方法论研讨
人工智能·科技·aigc·agi
AI创界者1 小时前
从零到一构建微服务架构HIS(医院信息系统):核心模块设计与高并发实战
人工智能·aigc