Skip to main content
Hooks intercept agent actions at lifecycle points so you can log, modify, or block them without changing agent code.
from praisonaiagents import Agent
from praisonaiagents.hooks import add_hook

@add_hook("before_tool")
def log_tools(event_data):
    print(f"Running tool: {event_data.tool_name}")

agent = Agent(name="SecureAssistant", instructions="You are a helpful assistant.")
agent.start("Help me organise my files")
The user sends a request; hooks intercept tools and lifecycle steps without changing agent code.

Quick Start

1

Simple Usage

Register a hook with add_hook and any agent picks it up automatically:
from praisonaiagents import Agent
from praisonaiagents.hooks import add_hook

@add_hook('before_tool')
def log_tools(event_data):
    print(f"Running tool: {event_data.tool_name}")

@add_hook('before_tool')
def block_delete(event_data):
    if "delete" in event_data.tool_name.lower():
        return "Delete operations are blocked"

agent = Agent(
    name="SecureAssistant",
    instructions="You are a helpful assistant."
)

agent.start("Help me organize my files")
Hook return values:
  • None or no return → Allow
  • False → Deny
  • "reason" → Deny with custom message
2

With HooksConfig

Attach a HooksConfig to a specific agent for scoped hooks:
from praisonaiagents import Agent, HooksConfig

def log_step(event_data):
    print(f"Step: {event_data}")

def on_tool(event_data):
    print(f"Tool called: {event_data.tool_name}")

agent = Agent(
    name="MonitoredAgent",
    instructions="You are a helpful assistant.",
    hooks=HooksConfig(
        on_step=log_step,
        on_tool_call=on_tool,
    )
)

agent.start("Write a haiku about Python")

Which Hook Point Should I Use?

Pick the lifecycle event that matches what you want to observe or block.

How It Works

Available Hook Events

EventWhen It Fires
before_toolBefore any tool executes
after_toolAfter a tool returns a result
before_tool_definitionsAfter tool list assembled, before sent to LLM — shape what the model sees
before_agentBefore the agent starts a turn
after_agentAfter the agent completes a turn
before_llmBefore an LLM API call
after_llmAfter an LLM API response
on_errorWhen any error occurs
on_retryBefore a retry attempt — fires on both sync and async LLM paths (post-PR #2386)
session_startWhen a session begins
session_endWhen a session ends
on_retry is emitted once per retryable error, just before the back-off sleep. It runs whether the LLM call is agent.chat(...) (sync) or await agent.achat(...) (async). Sync-registered callbacks on the async path are run in a thread executor — they cannot block the event loop.
Two lookalike fields. Function-style hooks return HookResult and set modified_input to rewrite the payload. Shell-command hooks parse into HookOutput and use modified_data for the same purpose. When you write a hook in Python, always use HookResult(decision="allow", modified_input={...}) — the internal Agent code reads .modified_input on hook results.

Configuration Options

HooksConfig SDK Reference

Full parameter reference for HooksConfig
from praisonaiagents import Agent, HooksConfig

agent = Agent(
    instructions="...",
    hooks=HooksConfig(
        on_step=my_step_callback,
        on_tool_call=my_tool_callback,
        middleware=[my_middleware],
    )
)
OptionTypeDefaultDescription
on_stepCallable | NoneNoneCalled on each agent step
on_tool_callCallable | NoneNoneCalled before each tool execution
middlewareList[]Middleware list applied to all events

Common Patterns

Security Filtering

from praisonaiagents import Agent
from praisonaiagents.hooks import add_hook

BLOCKED_TOOLS = {"delete_file", "execute_command", "drop_table"}

@add_hook('before_tool')
def security_filter(event_data):
    if event_data.tool_name in BLOCKED_TOOLS:
        return f"Tool '{event_data.tool_name}' is not allowed in this environment"

agent = Agent(
    name="SafeAgent",
    instructions="Help users manage their documents safely."
)

agent.start("Clean up the old log files")

Audit Logging

import json
from datetime import datetime
from praisonaiagents import Agent
from praisonaiagents.hooks import add_hook

@add_hook('before_tool')
def audit_before(event_data):
    print(json.dumps({
        "ts": datetime.utcnow().isoformat(),
        "event": "before_tool",
        "tool": event_data.tool_name,
    }))

@add_hook('after_tool')
def audit_after(event_data):
    print(json.dumps({
        "ts": datetime.utcnow().isoformat(),
        "event": "after_tool",
        "tool": event_data.tool_name,
    }))

agent = Agent(
    name="AuditAgent",
    instructions="Process user requests with full audit trail."
)

agent.start("Search for the latest AI research papers")

Tool Matching with HookRegistry

from praisonaiagents import Agent
from praisonaiagents.hooks import HookRegistry, HookEvent, HookResult

registry = HookRegistry()

@registry.on(HookEvent.BEFORE_TOOL)
def file_guard(event_data) -> HookResult:
    if event_data.tool_name.startswith("write_"):
        print(f"Write operation: {event_data.tool_name}")
    return HookResult.allow()

agent = Agent(
    name="GuardedAgent",
    instructions="You are a helpful assistant.",
    hooks=registry
)

agent.start("Create a summary and save it to output.txt")

Best Practices

Hooks run synchronously before/after each operation. Avoid network calls or heavy computation inside hook functions — use async queues for heavy processing.
The simplest hook contract: return nothing (or None) to allow, return a string with a reason to block. This keeps hooks readable.
add_hook registers hooks globally — all agents in the process obey them. Use HooksConfig when you need different rules per agent.
By default, hook exceptions are logged as warnings and execution continues. Set agent._strict_hooks = True in tests so hook failures surface immediately as errors.

Guardrails

Validate agent output quality with automatic retry

Callbacks

Observe agent events for UI and logging purposes