docs/results.md
When you call the Runner.run methods, you receive one of two result types:
RunResult][agents.result.RunResult] from Runner.run(...) or Runner.run_sync(...)RunResultStreaming][agents.result.RunResultStreaming] from Runner.run_streamed(...)Both inherit from [RunResultBase][agents.result.RunResultBase], which exposes the shared result surfaces such as final_output, new_items, last_agent, raw_responses, and to_state().
RunResultStreaming adds streaming-specific controls such as [stream_events()][agents.result.RunResultStreaming.stream_events], [current_agent][agents.result.RunResultStreaming.current_agent], [is_complete][agents.result.RunResultStreaming.is_complete], and [cancel(...)][agents.result.RunResultStreaming.cancel].
Most applications only need a few result properties or helpers:
| If you need... | Use |
|---|---|
| The final answer to show the user | final_output |
| A replay-ready next-turn input list with the full local transcript | to_input_list() |
| Rich run items with agent, tool, handoff, and approval metadata | new_items |
| The agent that should usually handle the next user turn | last_agent |
OpenAI Responses API chaining with previous_response_id | last_response_id |
| Pending approvals and a resumable snapshot | interruptions and to_state() |
Metadata about the current nested Agent.as_tool() invocation | agent_tool_invocation |
| Raw model calls or guardrail diagnostics | raw_responses and the guardrail result arrays |
The [final_output][agents.result.RunResultBase.final_output] property contains the final output of the last agent that ran. This is either:
str, if the last agent did not have an output_type definedlast_agent.output_type, if the last agent had an output type definedNone, if the run stopped before a final output was produced, for example because it paused on an approval interruption!!! note
`final_output` is typed as `Any`. Handoffs can change which agent finishes the run, so the SDK cannot statically know the full set of possible output types.
In streaming mode, final_output stays None until the stream has finished processing. See Streaming for the event-by-event flow.
These surfaces answer different questions:
| Property or helper | What it contains | Best for |
|---|---|---|
[input][agents.result.RunResultBase.input] | The base input for this run segment. If a handoff input filter rewrote the history, this reflects the filtered input the run continued with. | Auditing what this run actually used as input |
[to_input_list()][agents.result.RunResultBase.to_input_list] | An input-item view of the run. The default mode="preserve_all" keeps the converted history from new_items, except it does not append an exact session item occurrence already moved into SDK-default nested handoff history a second time; mode="normalized" prefers canonical continuation input when handoff filtering rewrites model history. | Manual chat loops, client-managed conversation state, and plain-item history inspection |
[new_items][agents.result.RunResultBase.new_items] | Rich [RunItem][agents.items.RunItem] wrappers with agent, tool, handoff, and approval metadata. | Logs, UIs, audits, and debugging |
[raw_responses][agents.result.RunResultBase.raw_responses] | Raw [ModelResponse][agents.items.ModelResponse] objects from each model call in the run. | Provider-level diagnostics or raw response inspection |
In practice:
to_input_list() when you want a plain input-item view of the run.to_input_list(mode="normalized") when you want the canonical local input for the next Runner.run(..., input=...) call after handoff filtering or nested handoff history rewrites.session=... when you want the SDK to load and save history for you.conversation_id or previous_response_id, usually pass only the new user input and reuse the stored ID instead of resending to_input_list().to_input_list() mode or new_items when you need the full converted history for logs, UIs, or audits.When SDK-default nested handoff history preserves a message item verbatim, Sessions, RunState, and to_input_list() track the exact owned occurrence rather than deduplicating by content. Identical messages that occurred separately remain separate; only the already-owned occurrence is kept from being appended a second time.
Unlike the JavaScript SDK, Python does not expose a separate output property containing only the model-format items newly generated during the run. Use new_items when you need SDK metadata, or inspect raw_responses when you need the raw model payloads.
Resubmitting computer-tool items as conversation input uses the raw Responses payload shape. Preview-model computer_call items preserve a single action, while gpt-5.5 computer calls can preserve batched actions[]. [to_input_list()][agents.result.RunResultBase.to_input_list] and [RunState][agents.run_state.RunState] keep whichever shape the model produced, so manually resubmitting those items as conversation input, pause/resume flows, and stored transcripts continue to work across both preview and GA computer-tool calls. Local execution results still appear as computer_call_output items in new_items.
[new_items][agents.result.RunResultBase.new_items] gives you the richest view of what happened during the run. Common item types are:
InputItem][agents.items.InputItem] for input admitted from RunState.pending_input immediately before a resumed model callMessageOutputItem][agents.items.MessageOutputItem] for assistant messagesReasoningItem][agents.items.ReasoningItem] for reasoning itemsToolSearchCallItem][agents.items.ToolSearchCallItem] and [ToolSearchOutputItem][agents.items.ToolSearchOutputItem] for Responses tool search requests and loaded tool-search resultsToolCallItem][agents.items.ToolCallItem] and [ToolCallOutputItem][agents.items.ToolCallOutputItem] for tool calls and their resultsToolApprovalItem][agents.items.ToolApprovalItem] for tool calls that paused for approvalMCPApprovalRequestItem][agents.items.MCPApprovalRequestItem], [MCPApprovalResponseItem][agents.items.MCPApprovalResponseItem], and [MCPListToolsItem][agents.items.MCPListToolsItem] for hosted MCP approvals and tool catalogsHandoffCallItem][agents.items.HandoffCallItem] and [HandoffOutputItem][agents.items.HandoffOutputItem] for handoff requests and completed transfersChoose new_items over to_input_list() whenever you need agent associations, tool outputs, handoff boundaries, or approval boundaries.
When you use hosted tool search, inspect ToolSearchCallItem.raw_item to see the search request the model emitted, and ToolSearchOutputItem.raw_item to see which namespaces, functions, or hosted MCP servers were loaded for that turn.
With Programmatic Tool Calling, the generated program is a ToolCallItem, ordinary child tool calls owned by that program are also ToolCallItem entries, and the matching program_output is a ToolCallOutputItem. Program-owned hosted MCP mcp_approval_request and mcp_list_tools items are exceptions: they become MCPApprovalRequestItem and MCPListToolsItem entries.
Raw items can be typed Responses objects or mappings. In particular, program-owned shell and apply-patch calls use mappings. Use a mapping-safe inspection pattern:
from collections.abc import Mapping
def raw_field(item, name):
raw_item = item.raw_item
if isinstance(raw_item, Mapping):
return raw_item.get(name)
return getattr(raw_item, name, None)
raw_type = raw_field(item, "type")
caller = raw_field(item, "caller")
caller_id = (
caller.get("caller_id")
if isinstance(caller, Mapping)
else getattr(caller, "caller_id", None)
)
For a program-owned child call, the type field of caller is program, and caller_id identifies the parent program call.
[last_agent][agents.result.RunResultBase.last_agent] contains the last agent that ran. This is often the best agent to reuse for the next user turn after handoffs.
In streaming mode, [RunResultStreaming.current_agent][agents.result.RunResultStreaming.current_agent] updates as the run progresses, so you can observe handoffs before the stream finishes.
If a tool needs approval, pending approvals are exposed in [RunResult.interruptions][agents.result.RunResult.interruptions] or [RunResultStreaming.interruptions][agents.result.RunResultStreaming.interruptions]. This can include approvals raised by direct tools, by tools reached after a handoff, or by nested [Agent.as_tool()][agents.agent.Agent.as_tool] runs.
Call [to_state()][agents.result.RunResult.to_state] to capture a resumable [RunState][agents.run_state.RunState], approve or reject the pending items, and then resume with Runner.run(...) or Runner.run_streamed(...).
When a [ToolCallOutputItem][agents.items.ToolCallOutputItem] output is a Pydantic model or dataclass, RunState serializes that output as structured data. RunState also traverses dictionaries, lists, and tuples and converts Pydantic models or dataclasses that it encounters in those containers; tuples are restored as lists after a JSON round trip. Other non-JSON-compatible values can fall back to their string representation, so return explicitly JSON-compatible data when an exact custom type must survive serialization.
from agents import Agent, Runner
agent = Agent(name="Assistant", instructions="Use tools when needed.")
result = await Runner.run(agent, "Delete temp files that are no longer needed.")
if result.interruptions:
state = result.to_state()
for interruption in result.interruptions:
state.approve(interruption)
result = await Runner.run(agent, state)
Use [RunState.add_input()][agents.run_state.RunState.add_input] when new user input arrives after a run pauses or stops after a completed turn, but before the unfinished run reaches its next model call. A string becomes a user message, and multiple calls preserve insertion order. The staged input is part of serialized RunState, so it survives to_json() / from_json() and to_string() / from_string() round trips.
state = result.to_state()
state.add_input("Also keep the generated report in the project folder.")
for interruption in state.get_interruptions():
state.approve(interruption)
result = await Runner.run(agent, state)
On resume, the runner applies both the current agent's input guardrails and the input guardrails from [RunConfig][agents.run.RunConfig] only to the staged input. When a client-managed [Session][agents.memory.session.Session] is configured, the runner converts the accepted staged input into a durable [InputItem][agents.items.InputItem] and awaits the session write before issuing the model request. Without a client-managed session or server-managed conversation, the runner converts the accepted staged input into an InputItem before issuing the model request. For a server-managed conversation, the input remains pending until the server request accepts it. Across serialization, resume, and replay-safe retries, the SDK preserves one durable InputItem occurrence. This SDK occurrence guarantee is not a provider-delivery guarantee: if a retry policy returns RetryDecision(approve_unsafe_replay=True) after a request may have reached the provider, the runner can resend the staged input and provider-side work can repeat. Successfully admitted input appears in new_items as an InputItem. Read [RunState.pending_input][agents.run_state.RunState.pending_input] for a detached copy, or call [RunState.clear_pending_input()][agents.run_state.RunState.clear_pending_input] to discard all staged input before resuming.
RunState.add_input() rejects a terminal state, a state with no remaining model turns, a state in which an accepted model response is awaiting local processing, and an interrupted state whose pending tool result may end the run before another model call. In those cases, finish the current run and start a new user turn instead.
For streaming runs, finish consuming [stream_events()][agents.result.RunResultStreaming.stream_events] first, then inspect result.interruptions and resume from result.to_state(). For the full approval flow, see Human-in-the-loop.
[last_response_id][agents.result.RunResultBase.last_response_id] is the latest model response ID from the run. Pass it back as previous_response_id on the next turn when you want to continue an OpenAI Responses API chain.
If you already continue the conversation with to_input_list(), session, or conversation_id, you usually do not need last_response_id. If you need every model response from a multi-step run, inspect raw_responses instead.
When a result comes from a nested [Agent.as_tool()][agents.agent.Agent.as_tool] run, [agent_tool_invocation][agents.result.RunResultBase.agent_tool_invocation] exposes immutable metadata about the enclosing Agent.as_tool() call:
tool_nametool_call_idtool_argumentsFor ordinary top-level runs, agent_tool_invocation is None.
This is especially useful inside custom_output_extractor, where you may need the enclosing Agent.as_tool() call's tool name, call ID, or raw arguments while post-processing the nested result. See Tools for the surrounding Agent.as_tool() patterns.
If you also need the parsed structured input for that nested run, read context_wrapper.tool_input. That is the field [RunState][agents.run_state.RunState] serializes generically for nested tool input, while agent_tool_invocation exposes metadata for the current nested invocation directly on the result.
[RunResultStreaming][agents.result.RunResultStreaming] inherits the same result surfaces above, but adds streaming-specific controls:
stream_events()][agents.result.RunResultStreaming.stream_events] to consume semantic stream eventscurrent_agent][agents.result.RunResultStreaming.current_agent] to track the active agent mid-runis_complete][agents.result.RunResultStreaming.is_complete] to see whether the streamed run has fully finishedcancel(...)][agents.result.RunResultStreaming.cancel] to stop the run immediately or after the current turnKeep consuming stream_events() until the async iterator finishes. A streaming run is not complete until that iterator ends, and summary properties such as final_output, interruptions, raw_responses, and session-persistence side effects may still be settling after the last visible token arrives.
If you call cancel(), continue consuming stream_events() so cancellation and cleanup can finish correctly.
Python does not expose a separate streamed completed promise or error property. Streaming failures that terminate the run are raised by stream_events(), and is_complete reflects whether the run has reached its terminal state.
[raw_responses][agents.result.RunResultBase.raw_responses] contains the raw model responses collected during the run. Multi-step runs can produce more than one response, for example across handoffs or repeated model/tool/model cycles.
[last_response_id][agents.result.RunResultBase.last_response_id] is just the ID from the last entry in raw_responses.
Each [ModelResponse][agents.items.ModelResponse] also exposes two diagnostics that apply to that individual model call:
request_id][agents.items.ModelResponse.request_id] is the transport request ID when the model adapter and transport propagate one. The built-in OpenAIResponsesModel and OpenAIChatCompletionsModel propagate an available server-generated x-request-id on their HTTP and SSE transport paths. When the configured endpoint is the OpenAI API, log a non-None value in production so you can correlate failures with OpenAI support; for an OpenAI-compatible provider or proxy, use that service's support channel instead. OpenAIResponsesWSModel currently leaves request_id as None. Third-party adapters do not guarantee request ID propagation. The AnyLLM Chat Completions adapter and LitellmModel currently leave request_id as None. The Agents SDK AnyLLM Responses adapter may also leave request_id as None when it normalizes a provider response without preserving the transport request ID.raw_usage][agents.items.ModelResponse.raw_usage] is an opt-in, JSON-compatible snapshot of the provider's usage payload before the Agents SDK normalizes the payload. Enable raw_usage with ModelSettings(preserve_raw_usage=True); see Preserving provider usage payloads.ModelResponse.request_id and ModelResponse.raw_usage can each be None, so handle these values as optional diagnostics rather than conversation state.
Agent-level guardrails are exposed as [input_guardrail_results][agents.result.RunResultBase.input_guardrail_results] and [output_guardrail_results][agents.result.RunResultBase.output_guardrail_results].
Tool guardrails are exposed separately as [tool_input_guardrail_results][agents.result.RunResultBase.tool_input_guardrail_results] and [tool_output_guardrail_results][agents.result.RunResultBase.tool_output_guardrail_results].
These arrays accumulate across the run, so they are useful for logging decisions, storing extra guardrail metadata, or debugging why a run was blocked.
[context_wrapper][agents.result.RunResultBase.context_wrapper] exposes your app context together with SDK-managed runtime metadata such as approvals, usage, and nested tool_input.
Usage is tracked on context_wrapper.usage. For streamed runs, the usage totals can lag until the stream's final chunks have been processed. See Context management for the full wrapper shape and persistence caveats.