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 stage | Session behavior |
|---|---|
for_agent, for_run, get_instructions | Runs during setup; dynamic instructions are evaluated once at connect. |
get_toolset, get_wrapper_toolset, prepare_tools | Contributes, wraps, and prepares local tools before connecting. |
get_native_tools | Contributes native tools before connecting; a dynamic native-tool function is resolved once against the connect-time context, like dynamic instructions. |
| Tool validation/execution hooks | Runs around each local function-tool call. |
handle_deferred_tool_calls | Resolves deferred requests inline; see deferred and approval-required tools. |
Graph node, model-request, get_wrapper_model, and output-processing hooks | Do 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.
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().
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.
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 field | Value in a realtime session |
|---|---|
ctx.model_settings | The merged RealtimeModelSettings the session was connected with. |
ctx.realtime | True for the whole run, including for_run and instruction functions that run before the connection exists. |
ctx.realtime_session | The live RealtimeSession in tool and on_event contexts; None in run hooks. |
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 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).