How to Use RunHooks and AgentHooks to Intercept Agent Lifecycle Events in openai-agents-python

Subclass RunHooksBase or AgentHooksBase from src/agents/lifecycle.py, override the specific lifecycle methods you need, and pass the instance to AgentRunner.run() or Runner.run() via the hooks= parameter to intercept execution without modifying core agent logic.

The openai-agents-python SDK exposes two distinct hook systems that let you tap into agent execution flows at different granularities. RunHooks and AgentHooks provide a non-intrusive way to log, monitor, and modify behavior during agent lifecycle events. Both systems are defined in src/agents/lifecycle.py and integrate with the execution loop through RunContextWrapper in src/agents/run_context.py.

Understanding RunHooks vs. AgentHooks

The SDK distinguishes between run-level and agent-level interception points:

RunHooks operate at the run level, managing the lifecycle of a single execution flow from creation through completion. These hooks fire during turn execution, tool calls, and error handling within src/agents/run_internal/run_loop.py.

AgentHooks operate at the agent instance level, capturing global events like initialization, shutdown, run creation, and state rewinds. These hooks manage cross-run concerns and agent lifespan events.

Both classes are generic (type-parameterized) and preserve your custom RunContext types throughout the execution chain, ensuring type-safe access to contextual data within hook implementations.

How the Hook Mechanism Works Internally

When you invoke AgentRunner.run() or Runner.run() as defined in src/agents/run.py, the execution flow enters src/agents/run_internal/run_loop.py. At predetermined interception points, the runner checks for registered hooks via RunContextWrapper in src/agents/run_context.py.

If hooks are provided, the wrapper forwards calls to your implementations. If no hooks are registered, the runner uses default no-op implementations from the base classes, ensuring zero performance overhead when interception is not needed.

Implementing RunHooks for Run-Level Interception

To intercept individual turns, tool executions, and run completion, subclass RunHooksBase from src/agents/lifecycle.py and implement the methods corresponding to the events you need.

from agents import AgentRunner, RunContext, RunHooksBase
from agents.llm import OpenAIChat
from agents.agent import SimpleAgent

# Define a custom context to carry request metadata

class MyRunContext(RunContext):
    request_id: str = "unknown"

# Implement run-level hooks

class LoggingRunHooks(RunHooksBase[MyRunContext, SimpleAgent]):
    async def on_start(self, ctx: MyRunContext) -> None:
        print(f"[Run start] request_id={ctx.request_id}")

    async def on_before_turn(self, ctx: MyRunContext) -> None:
        print(f"[Turn {ctx.turn}] about to call model…")

    async def on_after_turn(self, ctx: MyRunContext) -> None:
        print(f"[Turn {ctx.turn}] completed, tokens used={ctx.last_response.usage.total_tokens}")

    async def on_tool_error(self, ctx: MyRunContext, exc: Exception) -> None:
        print(f"[Tool error] {exc!r}")

# Build and run the agent with hooks

llm = OpenAIChat(model="gpt-4o-mini")
agent = SimpleAgent(llm=llm)
runner = AgentRunner(agent)

await runner.run(
    messages=[{"role": "user", "content": "Explain the Fibonacci sequence"}],
    context=MyRunContext(request_id="req-12345"),
    hooks=LoggingRunHooks(),
)

The RunHooksBase class provides methods including on_start, on_before_turn, on_after_turn, on_tool_start, on_tool_success, on_tool_error, on_finish, and on_error. Each receives the current RunContext, allowing you to inspect or log state without blocking the main execution flow.

Implementing AgentHooks for Agent-Level Interception

For global agent lifecycle events, use AgentHooksBase from src/agents/lifecycle.py. This class provides hooks for initialization, shutdown, run creation, and state rewinds.

from agents import AgentRunner, AgentHooksBase, AgentHookContext
from agents.llm import OpenAIChat
from agents.agent import SimpleAgent

class MetricsAgentHooks(AgentHooksBase[object, SimpleAgent]):
    async def on_agent_init(self, ctx: AgentHookContext) -> None:
        print("[Agent] initialising – allocate metrics bucket")

    async def on_agent_shutdown(self, ctx: AgentHookContext) -> None:
        print("[Agent] shutting down – flush metrics")

    async def on_run_created(self, ctx: AgentHookContext) -> None:
        print("[Run] new run started – start timer")

    async def on_run_rewind(self, ctx: AgentHookContext) -> None:
        print("[Run] rewind requested – reset timer")

# Build and run with agent-level hooks

llm = OpenAIChat(model="gpt-4o-mini")
agent = SimpleAgent(llm=llm)
runner = AgentRunner(agent)

await runner.run(
    messages=[{"role": "user", "content": "What is the weather today?"}],
    hooks=MetricsAgentHooks(),
)

AgentHooksBase methods include on_agent_init, on_agent_shutdown, on_run_created, on_run_rewind, and on_global_error. These fire outside the scope of any single run, making them ideal for resource management and cross-run persistence.

When to Use RunHooks vs. AgentHooks

Choose your hook type based on the scope of the event you need to intercept:

  • Use RunHooksBase when you need to instrument individual execution turns, handle tool-level failures, or log per-run metrics like token usage. These hooks fire within the single execution flow managed by src/agents/run_internal/run_loop.py.

  • Use AgentHooksBase when you need to manage global agent state, allocate resources during agent initialization, clean up during shutdown, or handle run rewinds and cross-run persistence. These hooks operate at the agent instance level independent of any single run.

Both systems are composable—you can provide instances of both hook types simultaneously to the same runner invocation. The RunContextWrapper in src/agents/run_context.py manages the delegation chain, ensuring both run-level and agent-level callbacks fire in the correct order without interference.

Summary

  • RunHooks and AgentHooks provide a non-intrusive mechanism to intercept agent lifecycle events in openai-agents-python without modifying core agent logic.
  • RunHooksBase (in src/agents/lifecycle.py) manages run-level events including turns, tool execution, and errors via methods like on_before_turn, on_tool_success, and on_finish.
  • AgentHooksBase (in src/agents/lifecycle.py) handles agent-level lifecycle events including initialization, shutdown, and run rewinds via on_agent_init, on_run_created, and on_run_rewind.
  • Hooks integrate through RunContextWrapper in src/agents/run_context.py and are invoked by the execution loop in src/agents/run_internal/run_loop.py.
  • Both hook types are generic and preserve custom RunContext types for type-safe access to contextual data.
  • Hooks incur zero performance overhead when not registered, as the runner uses default no-op implementations.

Frequently Asked Questions

What is the difference between RunHooks and AgentHooks in openai-agents-python?

RunHooks intercept events within a single execution flow or "run," such as individual model turns, tool executions, and run completion. AgentHooks operate at the agent instance level, handling global lifecycle events like agent initialization, shutdown, run creation, and state rewinds. According to the source code in src/agents/lifecycle.py, RunHooksBase focuses on per-run instrumentation while AgentHooksBase manages cross-run state and resource lifecycle.

How do I pass custom hooks to an agent runner?

You pass hook instances via the hooks= parameter when calling AgentRunner.run() or Runner.run(). As shown in src/agents/run.py, the runner accepts subclasses of either RunHooksBase or AgentHooksBase. The execution loop in src/agents/run_internal/run_loop.py then invokes your hook methods at the appropriate lifecycle points, wrapping them via RunContextWrapper from src/agents/run_context.py.

Can I use both RunHooks and AgentHooks simultaneously?

Yes, the hook systems are composable and designed to work together. You can provide instances of both RunHooksBase and AgentHooksBase to the same runner invocation. The RunContextWrapper class in src/agents/run_context.py manages the delegation chain, ensuring that both run-level and agent-level callbacks fire in the correct sequence without interfering with each other or blocking the main execution flow.

Do hooks impact performance if I don't register any?

No. When no hooks are provided, the runner uses default no-op implementations defined in RunHooksBase and AgentHooksBase. As implemented in openai-agents-python, the core execution loop in src/agents/run_internal/run_loop.py checks for hook presence and skips invocation overhead entirely when no hooks are registered, ensuring zero performance penalty for the opt-in interception mechanism.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →