feat(hooks): HookCenter typed-event hook system with plugin support#3564
Open
aiguozhi123456 wants to merge 15 commits intoHKUDS:nightlyfrom
Open
feat(hooks): HookCenter typed-event hook system with plugin support#3564aiguozhi123456 wants to merge 15 commits intoHKUDS:nightlyfrom
aiguozhi123456 wants to merge 15 commits intoHKUDS:nightlyfrom
Conversation
- 6 event dataclasses: BeforeIteration, OnStream, OnStreamEnd, BeforeExecuteTools, AfterIteration, FinalizeContent - HookHandler Protocol (async callable, runtime_checkable) - Return types: Modified(data), Deny(reason), HookResult union - Slotted dataclasses for memory efficiency
- HookCenter: typed-event registry with register_point/register/register_internal - HookSession: per-call container for internal handlers - emit() dispatch: Guard → Transform → Observe, internal before external - Guard Deny short-circuit, Transform chain pipeline, Observe sequential - Error isolation with per-handler reraise flag - wants_streaming() and finalize_content() dedicated methods - 29 tests covering all modes, ordering, errors, and edge cases
- discover_hook_plugins() scans entry_points(group='nanobot.hooks') - register_discovered() filters by enabled_plugins allowlist - Plugin contract: hook_events attribute, hook_streaming flag - Error isolation: individual plugin failures logged, others proceed - entry_points() full failure degrades gracefully - 19 tests covering discovery, allowlist, and error paths
- adapt_agent_hook() wraps legacy AgentHook methods as event handlers - Method→event mapping: 6 lifecycle events - AgentHookContext reconstruction from event dataclasses - _reraise flag propagation from AgentHook to handler registration - adapt_agent_hook_list() with CompositeHook recursive flattening - 24 tests covering all methods, reraise, and adapter patterns
…+U6) - AgentRunSpec: remove hook field, add center + session fields - AgentRunner.run(): emit typed events through HookCenter - All 27 hook call sites replaced with emit() + event dataclasses - _LoopHook and _SubagentHook adapted through HookCenter - Adapter shares mutable AgentHookContext via session.context - 162 tests pass — full backward compatibility
- Guard Deny check in runner BeforeExecuteTools (P0 HKUDS#1) - AfterIteration emit on max_iterations path (P0 HKUDS#3) - Allowlist check before ep.load() in discovery (P0 HKUDS#2) - Remove FinalizeContent dead payload fields (HKUDS#4) - Remove register_point dead code (HKUDS#5) - Docstring session.context coupling contract (HKUDS#6) - Extract _make_after_iteration helper (HKUDS#7) - Warning on non-dict Modified.data (HKUDS#8) - Dedup in register_internal (HKUDS#9)
- New docs/hook-plugin-guide.md: build, package, install hook plugins - Update docs/README.md: add hook plugin guide to Advanced Docs - Update docs/configuration.md: document hooks.enabled_plugins - Update docs/python-sdk.md: add event-based hook API documentation
Contributor
|
很有意思的功能,我觉得你可以想想能不能做成热加载 |
Contributor
Author
不太安全吧,现在nanobot的/restart其实挺方便的。 |
Guards can now return Deny(reason, abort=True) to immediately terminate the agent loop instead of just injecting a soft-denial tool result. BeforeIteration also gains guard support for early iteration blocking.
e9d242b to
9359c6a
Compare
Contributor
Author
|
@Re-bin @chengyongru 个人大致初步想法是这样的,有一些细节代码还可以优化。没新增hook位点是希望社区按需贡献。想问问你们的看法? |
…ksConfig HookCenter.discover() was never called in production paths — external hook plugins were silently ignored. Add HooksConfig to the config schema (enabled_plugins allowlist) and create a persistent HookCenter singleton in AgentLoop.__init__ that calls discover() once. SubagentManager shares the parent center instead of creating throwaway instances.
…d error path tests - Change plugin discovery to default-deny: enabled_plugins=null loads no plugins instead of all (P0 security fix) - Remove 'context' in dir() debug code in runner.py, replace with proper None-guarded _make_after_iteration call (P0) - Add 5 error-path tests: guard exceptions, transform pipeline resilience, finalize_content Deny, independent sessions (P2) - Update docs and config schema to reflect default-deny policy
…plugin load safety - BeforeIteration soft-deny (abort=False) now injects guard reason as a synthetic user message and continues the loop, matching BeforeExecuteTools soft-deny behavior - _call_finalize_handler detects coroutine returns from async handlers and logs a warning instead of corrupting agent output - _call_finalize_handler preserves original content when handler returns None (no-change signal) - _apply_modified discards non-dict Modified.data instead of replacing the live event object (prevents downstream crash) - Removed dead _METHOD_EVENT_MAP in adapters and unused @runtime_checkable on HookHandler protocol - Promoted misspelled Modified.data key log from debug to warning - Wrapped ep.load() in ThreadPoolExecutor with 10s timeout to prevent plugin load from blocking agent startup - Implemented hook_streaming plugin attribute wiring - Added test_runner_before_iteration_soft_deny_continues_loop
… pipeline artifacts
This was referenced May 4, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
构建 HookCenter 基于类型化事件的钩子系统,替换 AgentHook 的方法重写模式。外部开发者现在可以通过
entry_points(group="nanobot.hooks")分发 hook plugin,handler 支持 observe/transform/guard 三种模式。旧 AgentHook 通过兼容适配器完全向后兼容。Context
nanobot 当前 AgentHook 系统存在三个瓶颈:添加 hook 位点需修改基类和 CompositeHook(O(n)),外部开发者无法自主分发 hook plugin(仅构造函数注入),错误处理不一致(async 方法有隔离但 finalize_content 没有)。Channel plugin 已有成熟的 entry_points 模式,但 hooks 完全没有。
nanobot/hooks/目录已预分配但空置。Why
这让 nanobot 从一个"只有内部开发者能扩展 hook"的框架变成一个"任何人都能写和分发 hook plugin"的生态。同时让内部开发者能零摩擦地添加新 hook 位点(定义事件类 + emit 一行),不再受 6 方法耦合的限制。
Solve
引入
nanobot/hooks/模块,核心设计:emit()分派引擎,Guard→Transform→Observe 顺序执行,内部 handler 先于外部nanobot.hooks组),带enabled_pluginsallowlist 安全控制_LoopHook/_SubagentHook保持内联abort=False(默认)注入 tool result 继续循环,abort=True终止 agent loopschema.py,AgentLoop.__init__创建持久化 HookCenter 并在启动时调用discover(config),SubagentManager 复用父 centerChanges
核心修改 +1,338 / -117,测试 +1,832 / -64,文档 +384 / -29。
nanobot/hooks/__init__.pynanobot/hooks/event_types.pynanobot/hooks/protocols.pynanobot/hooks/center.pynanobot/hooks/discovery.pynanobot/hooks/adapters.pynanobot/agent/runner.pynanobot/agent/loop.pynanobot/agent/subagent.pynanobot/config/schema.pynanobot/nanobot.pynanobot/cli/commands.pynanobot/agent/hook.pytests/hooks/test_center.pytests/hooks/test_discovery.pytests/hooks/test_adapters.pytests/agent/test_runner.pydocs/hook-plugin-guide.mddocs/configuration.mdCode review 修复(
04cb55e9):abort=False)注入 Deny 原因到 conversation 并继续循环(与 BeforeExecuteTools 一致)_call_finalize_handler检测 async handler 返回 coroutine,防止输出 corruption_call_finalize_handlerhandler 返回None保留原 content("未修改"语义)_apply_modified丢弃非 dictModified.data而非替换 event 对象ep.load()包装进ThreadPoolExecutor+ 10s 超时,防止阻塞启动hook_streamingplugin 属性完整 wiring_METHOD_EVENT_MAP、@runtime_checkable)Test
tests/hooks/test_center.py— 32 tests: HookCenter dispatch, Guard/Transform/Observe ordering, dedup, error isolation, wants_streaming, finalize_content, Deny.aborttests/hooks/test_discovery.py— 19 tests: entry_points discovery, allowlist filtering, error pathstests/hooks/test_adapters.py— 24 tests: AgentHook adapter, method mapping, reraise propagation, CompositeHook flatteningtests/agent/test_runner.py— 76 tests: 全部通过(含 BeforeIteration 软拒绝 + Deny abort 硬中断双模式集成测试)tests/agent/test_hook_composite.py— 18 tests: 全部通过Enhancement Direction
emit()固定 Guard→Transform→Observe 顺序执行。某些场景需要打破此约束:例如 observe handler 需在 guard 之前执行(日志审计先于拒绝判定),或 transform handler 需要绕过 guard 直接修改事件。可在register_point()时声明该事件类型的分派策略(如"free"模式按注册顺序执行而非按 mode 分组),或在emit()时接受order参数覆盖默认行为priority: int(数值越小越先执行),同类事件多插件时确定性排序;(2) 用户层 —config.hooks.plugin_priority映射表覆盖默认值,运维可调整生产环境插件顺序无需改代码nanobot hooks inspect