AgentHook¶
AgentHook is the core router type for custom callback apps.
It provides a decorator-based API for registering hook handlers while keeping provider-specific parsing and response rendering below the app layer.
Think "FastAPI for hook callbacks": you implement business logic against typed event objects, and Agent Hooks handles provider payload normalization plus provider-specific response wire shapes.
Decorator routes
Register behavior with handlers like @app.permission(), @app.stop(), and @app.notification().
Typed event injection
Write against PermissionRequestEvent, StopEvent, CallbackRequest, and DisplayTransport instead of raw JSON.
Reusable dependencies
Factor shared context into one-level Depends(...) helpers without dropping down to raw payload plumbing.
Unified schema
Claude PermissionRequest and Codex PreToolUse both arrive as the same normalized permission event.
What You Write Vs What Agent Hooks Handles¶
| You write | Agent Hooks handles |
|---|---|
| business rules for allow, deny, notify, or stop behavior | provider detection and payload parsing |
| typed handlers with route decorators | mapping raw provider event names into normalized events |
generic responses like HookResponse(...) |
rendering the correct provider response JSON |
a button choice like DialogButton.ALLOW_ONCE |
building provider-specific permission payloads |
Minimal Example¶
from __future__ import annotations
from agent_hooks import (
AgentHook,
HookResponse,
PermissionRequestEvent,
StopEvent,
build_permission_response,
)
from agent_hooks.enums import DialogButton
app = AgentHook()
@app.permission()
def permission_handler(hook_event: PermissionRequestEvent):
if hook_event.tool_name == "Bash" and hook_event.tool_input.command.startswith("git status"):
return build_permission_response(DialogButton.ALLOW_ONCE, hook_event)
return build_permission_response(DialogButton.DENY, hook_event)
@app.stop()
def stop_handler(hook_event: StopEvent) -> HookResponse:
return HookResponse(
suppress_output=False,
system_message=f"Handled locally for {hook_event.provider.value}.",
)
build_permission_response() lets you choose the policy outcome once and have Agent Hooks render the right provider-specific permission response. HookResponse(...) is the generic top-level response model for non-permission events.
One-Level Dependencies¶
You can extract reusable handler inputs with Depends(...):
from agent_hooks import CallbackRequest, Depends
def build_command(request: CallbackRequest) -> str:
return request.payload.tool_input.command
@app.permission()
def permission_handler(command: str = Depends(build_command)):
...
Dependency callables can receive the same built-in injections as route handlers, but nested dependencies are not supported.
Yield-based dependencies are also supported for resource lifecycles. If a dependency yields one value, that value is injected and the generator is resumed for cleanup after the handler completes.
Running The Router¶
Directly:
Through the CLI:
Route Decorators¶
AgentHook exposes the following route decorators:
notification()permission()pre_tool_use()session_start()user_prompt_submit()post_tool_use()stop()stop_failure()middleware()
pre_tool_use() is an alias for permission(), which keeps Codex PreToolUse aligned with the provider-neutral permission event model.
Response Primitives¶
Out-of-the-box response building is intentionally small and typed:
HookResponsefor generic top-level response fields such ascontinue_,stop_reason,system_message,decision, andreasonbuild_permission_response(DialogButton, hook_event)when you want Agent Hooks to generate the correct provider-specific permission payloadAppleScriptDialogResponseas the concrete normalized permission-response model returned bybuild_permission_response()
Fallback Handler¶
AgentHook defaults to fallback_handler=DefaultHookHandler().
That means:
- if a route is registered, the route runs
- if a route is missing, the default fallback handler can still handle supported built-in behavior
Set fallback_handler=None if you want your router to fully own the events it registers and return empty responses for everything else.
You can also pass your own fallback handler object:
from agent_hooks import AgentHook, DefaultHookHandler
app = AgentHook(fallback_handler=DefaultHookHandler())
Provider Default¶
You can optionally bind a default provider at router construction time:
That provider becomes the default for parsing and response rendering unless the CLI or runtime config overrides it.