ADR-002: Observer Pattern Replaces Hooks Dict¶
Status: Accepted (hooks deprecated, not yet removed) Date: 2026-03-15 Deciders: Core maintainers
Context¶
The original notification system was a hooks dict on AgentConfig — string-keyed callbacks with 11 events. In v0.15.0, AgentObserver was added as a class-based alternative with 25 events and richer data (run_id, call_id, system_prompt). Both systems coexisted, requiring every notification site to call both _call_hook() and _notify_observers().
By v0.16.4, this dual system was:
- Error-prone: New events were sometimes added to observers but forgotten in hooks.
- Noisy: ~55 _call_hook calls alongside ~55 _notify_observers calls in core.py.
- Type-unsafe: Hook names were strings — hooks["on_toll_start"] (typo) would silently do nothing.
Decision¶
- Create
_HooksAdapter(AgentObserver)that wraps a hooks dict as an observer, mapping the 11 hook events to observer methods. - In
Agent.__init__, whenconfig.hooksis set, emitDeprecationWarningand prepend a_HooksAdapterto the observers list. - Remove all
_call_hook()calls — hooks are now served through the single observer pipeline. - Add
AsyncAgentObserverfor async-native integrations (blocking and non-blocking modes).
Rationale¶
- Single notification path: One pipeline to maintain, one place to add new events.
- Backward compatible: The adapter makes existing hooks work transparently. Users see a deprecation warning but nothing breaks.
- Type safety: Observer methods are real Python methods — typos are caught by IDE autocompletion and mypy.
- Richer data: Observers receive
run_idfor correlation,call_idfor parallel tool matching,system_promptfor debugging — data that hooks never had.
Consequences¶
- Positive: core.py lost ~55 lines of
_call_hookcalls. The notification logic is consolidated. - Positive:
AsyncAgentObserverenables async DB writes without blocking the agent loop. - Negative: Users with existing
hooksusage seeDeprecationWarning. Migration is straightforward: create anAgentObserversubclass and move hook logic to the correspondingon_*methods. - Decision: No removal timeline set. Hooks remain functional via the adapter indefinitely. Removal will be evaluated for v0.18.0+.