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
RunHooksBasewhen 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 bysrc/agents/run_internal/run_loop.py. -
Use
AgentHooksBasewhen 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-pythonwithout modifying core agent logic. - RunHooksBase (in
src/agents/lifecycle.py) manages run-level events including turns, tool execution, and errors via methods likeon_before_turn,on_tool_success, andon_finish. - AgentHooksBase (in
src/agents/lifecycle.py) handles agent-level lifecycle events including initialization, shutdown, and run rewinds viaon_agent_init,on_run_created, andon_run_rewind. - Hooks integrate through
RunContextWrapperinsrc/agents/run_context.pyand are invoked by the execution loop insrc/agents/run_internal/run_loop.py. - Both hook types are generic and preserve custom
RunContexttypes 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:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →