Skip to content

Capabilities and hooks

A capability attached to the agent or passed to realtime(capabilities=...) participates in a realtime session where its lifecycle maps onto a persistent connection. Third-party capabilities load exactly the same way as in a regular run; nothing realtime-specific is required of them.

Capability stages in a session

Capability stageSession behavior
for_agent, for_run, get_instructionsRuns during setup; dynamic instructions are evaluated once at connect.
get_toolset, get_wrapper_toolset, prepare_toolsContributes, wraps, and prepares local tools before connecting.
get_native_toolsContributes native tools before connecting; a dynamic native-tool function is resolved once against the connect-time context, like dynamic instructions.
Tool validation/execution hooksRuns around each local function-tool call.
handle_deferred_tool_callsResolves deferred requests inline; see deferred and approval-required tools.
Graph node, model-request, get_wrapper_model, and output-processing hooksDo not run; no agent graph, model request, or output-processing stage exists.

All regular tool validation and tool execution hooks — before, after, wrap, and on_error for both stages — run around every local function-tool call exactly as in a standard run, retries and all. What does not run is anything tied to the request-response graph: node hooks, model request hooks such as before_model_request, and output validation and output processing hooks — a session has no graph nodes, no per-request boundary, and no output stage.

Opening a session emits a UserWarning for each capability that implements any of those hooks, naming the capability and its hooks, since what it does there won’t happen in the session — for example, ReinjectSystemPrompt works through before_model_request. A Hooks capability warns only for the hooks it registers. Built-ins whose behavior the session provides another way, such as Instrumentation, don’t warn.

Run hooks

before_run, after_run, wrap_run, and on_run_error run hooks run once around the session — a realtime session is a run — with the same close-boundary recovery and result-transformation semantics as iter().

The event stream

wrap_run_event_stream wraps the consumer-facing session iterator. It can observe or transform shared AgentStreamEvent members and realtime-only RealtimeSessionEvent members (see the event reference) without changing history or tool execution. There is no event_stream_handler parameter on realtime(); a handler-style consumer is attached with the ProcessEventStream capability, which works through this same stream.

Custom events and capability events emitted with ctx.emit() from tools and hooks flow through this stream, and reach on_event listeners, as they do in iter(). Events emitted before the session connects, from before_run, lead the stream; a tool’s events land between its FunctionToolCallEvent and FunctionToolResultEvent. Events emitted from after_run come after the stream has ended, so as in iter() they aren’t delivered, except to the listeners of an immediately dispatched event.

Model settings and RunContext

get_model_settings() may run during capability setup, but regular model settings do not configure a realtime model. Pass RealtimeModelSettings through realtime(model_settings=...) instead. Inside session hooks and tools, the RunContext reflects the session:

RunContext fieldValue in a realtime session
ctx.model_settingsThe merged RealtimeModelSettings the session was connected with.
ctx.realtimeTrue for the whole run, including for_run and instruction functions that run before the connection exists.
ctx.realtime_sessionThe live RealtimeSession in tool and on_event contexts; None in run hooks.

Seeded history is not processed

History-processing capabilities do not transform message_history before it is seeded into a session; preprocess the history before opening the session when filtering or redaction is required.

Deferred capability loading

Deferred capabilities load in a session much as they do in a regular run: the capability catalog is part of the session’s instructions, and the model calls the load_capability tool. A session can’t state the loaded capability’s instructions in the system voice the way a regular run does, so the tool returns them as its result, which works on every provider. When a session is seeded with the history of a regular run that loaded a capability, the session’s instructions include that capability’s instructions instead. What a session cannot do yet is advertise new tools mid-conversation: Pydantic AI fixes the connection’s tools when it opens (OpenAI and xAI accept a mid-session tool update; #7288), so opening a session with a defer_loading=True capability that contributes tools or native tools raises UserError before connecting — accepting it would silently provide less than requested. Loading those tools on providers that accept the update is tracked in #7191.

Loading a capability from code with ctx.load_capability() isn’t supported in a session: it raises UserError (see Loading a capability from code).