# How to Implement Pre and Post Hooks in Agno Agents for Custom Logic

> Implement pre and post hooks in Agno agents to add custom logic for validation logging telemetry and response modification easily without changing core code.

- Repository: [Agno/agno](https://github.com/agno-agi/agno)
- Tags: how-to-guide
- Published: 2026-02-23

---

**Agno agents support pre-hooks and post-hooks that execute automatically around every run, enabling custom logic for input validation, logging, telemetry, and response modification without altering core agent code.**

The Agno framework provides a robust hook system that allows developers to implement pre and post hooks in Agno agents for custom logic execution. These hooks integrate seamlessly into the agent's execution pipeline, running automatically before and after the LLM processing cycle. By leveraging the hook architecture defined in the `agno-agi/agno` repository, you can inject observability, validation, or transformation logic while maintaining clean separation from the core agent implementation.

## Understanding the Hook Architecture

Agno's **Agent** object supports two distinct hook pipelines that wrap every `run()` and `arun()` invocation.

### Pre-Hooks vs Post-Hooks

**Pre-hooks** execute immediately after the session loads but before any tool calls or LLM processing occurs. They receive the `RunInput` object and can modify it before the agent processes the request.

**Post-hooks** run after the LLM assembles its response but before the final `RunOutput` returns to the caller. They receive the completed `RunOutput` for logging, validation, or side effects.

### Hook Signatures and Context

Both hook types receive rich context objects. Pre-hooks use the signature:

```python
def hook(
    run_input: RunInput,
    agent: Agent,
    session: AgentSession,
    run_context: RunContext,
    user_id: Optional[str] = None,
    debug_mode: Optional[bool] = None,
    **extra
) -> None

```

Post-hooks substitute `run_output: RunOutput` for `run_input`. Async variants use `async def` and are executed via `aexecute_pre_hooks` and `aexecute_post_hooks` in [`libs/agno/agno/agent/_hooks.py`](https://github.com/agno-agi/agno/blob/main/libs/agno/agno/agent/_hooks.py).

## Implementing Pre-Hooks in Agno Agents

### Basic Synchronous Pre-Hook

Create a function that logs incoming prompts before the agent processes them:

```python

# my_hooks.py

def log_run_start(run_input, agent, session, run_context, **_):
    """Log the incoming prompt for observability."""
    print(f"[Hook] Agent {agent.name!r} is about to run:")
    print(f"   Prompt: {run_input.messages[-1].content!r}")

```

Attach the hook via the `pre_hooks` parameter defined in [`libs/agno/agno/agent/agent.py`](https://github.com/agno-agi/agno/blob/main/libs/agno/agno/agent/agent.py):

```python
from agno.agent.agent import Agent
from my_hooks import log_run_start

agent = Agent(
    name="DemoAgent",
    model=...,  # your model configuration

    pre_hooks=[log_run_start],
)

response = agent.run("Explain the difference between AI and ML.")
print(response.output)

```

### Modifying Input with Pre-Hooks

Pre-hooks receive `run_input` by reference, allowing mutation before the LLM sees the data:

```python
def inject_system_prompt(run_input, **_):
    """Prepend a system-level instruction to every run."""
    system_msg = {"role": "system", "content": "You are a friendly assistant."}
    run_input.messages.insert(0, system_msg)

```

The modified `run_input` propagates downstream because the hook operates on the same object instance passed to the LLM.

## Implementing Post-Hooks in Agno Agents

### Asynchronous Post-Hooks with Background Tasks

Post-hooks support async execution and can run as FastAPI background tasks when `_run_hooks_in_background` is enabled:

```python

# async_hooks.py

import aiohttp

async def send_telemetry(run_output, agent, session, run_context, **_):
    """Fire-and-forget telemetry to an external endpoint."""
    async with aiohttp.ClientSession() as client:
        await client.post(
            "https://telemetry.example.com/ingest",
            json={"run_id": run_output.run_id, "tokens": run_output.token_usage},
        )

```

Configure the agent to schedule hooks as background tasks:

```python
from agno.agent.agent import Agent
from async_hooks import send_telemetry

agent = Agent(
    name="TelemetryAgent",
    model=...,
    post_hooks=[send_telemetry],
    _run_hooks_in_background=True,
)

output = await agent.arun("Summarize the latest news.")
print(output.output)

```

The implementation in [`libs/agno/agno/agent/_hooks.py`](https://github.com/agno-agi/agno/blob/main/libs/agno/agno/agent/_hooks.py) handles background task scheduling via `background_tasks.add_task()`, allowing the agent to return responses immediately while hooks execute non-blocking side effects.

## Combining Multiple Hooks and Execution Order

You can attach multiple hooks to a single agent. They execute sequentially in the order defined:

```python
agent = Agent(
    name="MultiHookAgent",
    model=...,
    pre_hooks=[log_run_start, inject_system_prompt],
    post_hooks=[send_telemetry, lambda ro, **_: print("Run finished!")],
    _run_hooks_in_background=False,
)

```

The `enumerate(hooks)` pattern in `execute_pre_hooks` and `execute_post_hooks` ensures deterministic sequencing. The `filter_hook_args` utility in [`libs/agno/agno/utils/hooks.py`](https://github.com/agno-agi/agno/blob/main/libs/agno/agno/utils/hooks.py) strips unused arguments, allowing hooks to define minimal signatures regardless of the full parameter set available.

## Key Implementation Files

Understanding the source structure helps when debugging hook behavior:

- **[`libs/agno/agno/agent/agent.py`](https://github.com/agno-agi/agno/blob/main/libs/agno/agno/agent/agent.py)** – Defines the `Agent` dataclass including `pre_hooks`, `post_hooks`, and `_run_hooks_in_background` fields.
- **[`libs/agno/agno/agent/_hooks.py`](https://github.com/agno-agi/agno/blob/main/libs/agno/agno/agent/_hooks.py)** – Contains `execute_pre_hooks`, `aexecute_pre_hooks`, `execute_post_hooks`, and `aexecute_post_hooks` which orchestrate hook execution, streaming, and background task scheduling.
- **[`libs/agno/agno/agent/_run.py`](https://github.com/agno-agi/agno/blob/main/libs/agno/agno/agent/_run.py)** – Entry point that invokes hook executors from the main run loop and integrates hook iterators into the output event stream.
- **[`libs/agno/agno/utils/hooks.py`](https://github.com/agno-agi/agno/blob/main/libs/agno/agno/utils/hooks.py)** – Provides `filter_hook_args` for argument filtering and `copy_args_for_background` for background task preparation.

## Summary

- **Pre-hooks** execute before LLM processing via `execute_pre_hooks` in [`libs/agno/agno/agent/_hooks.py`](https://github.com/agno-agi/agno/blob/main/libs/agno/agno/agent/_hooks.py), receiving `RunInput` for validation or modification.
- **Post-hooks** execute after response assembly via `execute_post_hooks`, receiving `RunOutput` for telemetry or logging.
- **Async support** is available through `aexecute_pre_hooks` and `aexecute_post_hooks`, with optional FastAPI background task execution when `_run_hooks_in_background=True`.
- **Argument filtering** via `filter_hook_args` allows hooks to accept only the parameters they need.
- **Multiple hooks** execute sequentially in list order, enabling composable middleware patterns.

## Frequently Asked Questions

### Can pre-hooks modify the input before it reaches the LLM?

Yes. Pre-hooks receive the `run_input` object by reference. Mutations such as inserting system messages or modifying the message list persist downstream because the hook operates on the same object instance passed to the LLM.

### What happens if a hook raises an exception?

Non-validation exceptions are caught and logged via `log_error` or `log_exception`, allowing the run to continue. However, `InputCheckError` and `OutputCheckError` exceptions are re-raised immediately to abort the run, enabling strict validation workflows.

### How do I run hooks asynchronously without blocking the agent response?

Set `_run_hooks_in_background=True` on the Agent instance. This schedules hooks as FastAPI background tasks using `background_tasks.add_task()`, allowing the agent to return the response immediately while hooks execute non-blocking side effects like external logging or analytics.

### Can I use the same hook function for both pre and post hooks?

Technically yes, but you must design the function signature to accept both `run_input` and `run_output` parameters, or use `**kwargs` to ignore unused arguments. The `filter_hook_args` utility in [`libs/agno/agno/utils/hooks.py`](https://github.com/agno-agi/agno/blob/main/libs/agno/agno/utils/hooks.py) strips unused arguments, so a flexible signature can work for both hook types.