From d88be08bfd80875fda94d1d84f7d3595115cea47 Mon Sep 17 00:00:00 2001 From: Lingao Meng Date: Wed, 8 Apr 2026 09:10:44 +0800 Subject: [PATCH] refactor(hook): add reraise flag to AgentHook and remove _LoopHookChain Add reraise parameter to AgentHook so hooks can opt out of exception swallowing in CompositeHook._for_each_hook_safe. _LoopHook sets reraise=True to let its exceptions propagate. _LoopHookChain is removed and replaced with CompositeHook([loop_hook] + extra_hooks). Signed-off-by: Lingao Meng --- nanobot/agent/hook.py | 7 +++++++ nanobot/agent/loop.py | 41 ++--------------------------------------- 2 files changed, 9 insertions(+), 39 deletions(-) diff --git a/nanobot/agent/hook.py b/nanobot/agent/hook.py index 827831ebd..33db416ed 100644 --- a/nanobot/agent/hook.py +++ b/nanobot/agent/hook.py @@ -29,6 +29,9 @@ class AgentHookContext: class AgentHook: """Minimal lifecycle surface for shared runner customization.""" + def __init__(self, reraise: bool = False) -> None: + self._reraise = reraise + def wants_streaming(self) -> bool: return False @@ -69,6 +72,10 @@ class CompositeHook(AgentHook): async def _for_each_hook_safe(self, method_name: str, *args: Any, **kwargs: Any) -> None: for h in self._hooks: + if h._reraise: + await getattr(h, method_name)(*args, **kwargs) + continue + try: await getattr(h, method_name)(*args, **kwargs) except Exception: diff --git a/nanobot/agent/loop.py b/nanobot/agent/loop.py index 66d765d00..d549d6d4a 100644 --- a/nanobot/agent/loop.py +++ b/nanobot/agent/loop.py @@ -54,6 +54,7 @@ class _LoopHook(AgentHook): chat_id: str = "direct", message_id: str | None = None, ) -> None: + super().__init__(reraise=True) self._loop = agent_loop self._on_progress = on_progress self._on_stream = on_stream @@ -108,44 +109,6 @@ class _LoopHook(AgentHook): def finalize_content(self, context: AgentHookContext, content: str | None) -> str | None: return self._loop._strip_think(content) - -class _LoopHookChain(AgentHook): - """Run the core hook before extra hooks.""" - - __slots__ = ("_primary", "_extras") - - def __init__(self, primary: AgentHook, extra_hooks: list[AgentHook]) -> None: - self._primary = primary - self._extras = CompositeHook(extra_hooks) - - def wants_streaming(self) -> bool: - return self._primary.wants_streaming() or self._extras.wants_streaming() - - async def before_iteration(self, context: AgentHookContext) -> None: - await self._primary.before_iteration(context) - await self._extras.before_iteration(context) - - async def on_stream(self, context: AgentHookContext, delta: str) -> None: - await self._primary.on_stream(context, delta) - await self._extras.on_stream(context, delta) - - async def on_stream_end(self, context: AgentHookContext, *, resuming: bool) -> None: - await self._primary.on_stream_end(context, resuming=resuming) - await self._extras.on_stream_end(context, resuming=resuming) - - async def before_execute_tools(self, context: AgentHookContext) -> None: - await self._primary.before_execute_tools(context) - await self._extras.before_execute_tools(context) - - async def after_iteration(self, context: AgentHookContext) -> None: - await self._primary.after_iteration(context) - await self._extras.after_iteration(context) - - def finalize_content(self, context: AgentHookContext, content: str | None) -> str | None: - content = self._primary.finalize_content(context, content) - return self._extras.finalize_content(context, content) - - class AgentLoop: """ The agent loop is the core processing engine. @@ -359,7 +322,7 @@ class AgentLoop: message_id=message_id, ) hook: AgentHook = ( - _LoopHookChain(loop_hook, self._extra_hooks) + CompositeHook([loop_hook] + self._extra_hooks) if self._extra_hooks else loop_hook )