Hook System
“Hang on the Loop, Don't Write into It”
Cross-cutting behavior belongs around the loop, not tangled inside it.
钩子是生命周期回调函数,插件可在特定执行点注册:工具执行前后、消息创建时、配置加载时等。这种设计既保持了核心循环的简洁,又提供了丰富的可扩展性。慢钩子不会阻塞 agent。钩子执行带有超时机制,失败会被记录而不影响主流程。
架构流程图
问题:横切关注点而不污染核心
日志、遥测、自定义验证和审计追踪都属于横切关注点,不应混杂在 agent 循环或工具处理函数中。如果没有钩子机制,开发者面临两难:要么用可选回调污染核心逻辑,使其难以维护;要么 fork 代码库,导致无法跟随上游升级。钩子通过在核心循环周围定义扩展点来解决这一问题。任意数量的插件可为同一钩子注册,并按注册顺序依次执行。核心循环从不导入插件代码,只检查特定事件名称是否有已注册的钩子。
钩子设计:带类型化载荷的命名事件
每个钩子都是一个命名事件,配有类型化的上下文对象。'tool.execute.before' 接收 { tool, input, opts },'tool.execute.after' 接收 { tool, result, duration }。这种结构化设计使钩子具备可发现性(事件名遵循统一约定)、类型安全性(每个事件定义明确的载荷结构)和隔离性(单个钩子失败不会影响其他钩子,超时机制保证了这一点)。命名约定 domain.event 借鉴了 DOM 事件模式,使每个钩子的意图一目了然。'tool.execute.before' 在工具执行前触发,'config' 在配置加载时触发。
设计决策
钩子带有可配置的超时机制。行为异常的插件,无论是陷入死循环还是等待缓慢的网络调用,都会在超时后被终止。错误被记录,核心循环则不受影响地继续执行。
钩子的注册顺序非常重要。先注册的插件,其钩子优先执行。这实现了基于优先级的行为编排:安全插件可在日志插件记录输入之前先行验证。
对比 Claude Code
Claude Code 不提供传统意义上的插件系统。OpenCode 的生命周期钩子(beforeToolExecute、afterToolExecute、beforeLlmCall、onConfigLoad)是其显著特性。这些钩子允许任何外部代码拦截并增强核心循环的功能,而无需修改源代码。这更接近 VS Code 的扩展模型,而非 Claude Code 中的任何机制。
深入设计决策
钩子优于继承或中间件
插件在特定生命周期点注册钩子,而不是继承 agent。这保持了核心循环的完整性,并使插件行为可组合。
钩子默认必须非阻塞
慢速或损坏的钩子不应阻塞 agent。每个钩子有超时,失败会被记录而不是传播。即使钩子出错,agent 也继续运行。