Skip to content

Results

When you call the Runner.run methods, you receive one of two result types:

Both inherit from 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(), current_agent, is_complete, and cancel(...).

Choose the right result surface

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

Final output

The final_output property contains the final output of the last agent that ran. This is either:

  • a str, if the last agent did not have an output_type defined
  • an object of type last_agent.output_type, if the last agent had an output type defined
  • None, 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.

Input, next-turn history, and new items

These surfaces answer different questions:

Property or helper What it contains Best for
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() 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 Rich RunItem wrappers with agent, tool, handoff, and approval metadata. Logs, UIs, audits, and debugging
raw_responses Raw ModelResponse objects from each model call in the run. Provider-level diagnostics or raw response inspection

In practice:

  • Use to_input_list() when you want a plain input-item view of the run.
  • Use 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.
  • Use session=... when you want the SDK to load and save history for you.
  • If you are using OpenAI server-managed state with conversation_id or previous_response_id, usually pass only the new user input and reuse the stored ID instead of resending to_input_list().
  • Use the default 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() and 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

new_items gives you the richest view of what happened during the run. Common item types are:

Choose 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.

Continue or resume the conversation

Next-turn agent

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 updates as the run progresses, so you can observe handoffs before the stream finishes.

Interruptions and run state

If a tool needs approval, pending approvals are exposed in RunResult.interruptions or RunResultStreaming.interruptions. This can include approvals raised by direct tools, by tools reached after a handoff, or by nested Agent.as_tool() runs.

Call to_state() to capture a resumable RunState, approve or reject the pending items, and then resume with Runner.run(...) or Runner.run_streamed(...).

When a 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)

Add input before resuming

Use 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 only to the staged input. When a client-managed Session is configured, the runner converts the accepted staged input into a durable 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 for a detached copy, or call 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() first, then inspect result.interruptions and resume from result.to_state(). For the full approval flow, see Human-in-the-loop.

Server-managed continuation

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.

Agent-as-tool metadata

When a result comes from a nested Agent.as_tool() run, agent_tool_invocation exposes immutable metadata about the enclosing Agent.as_tool() call:

  • tool_name
  • tool_call_id
  • tool_arguments

For 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 serializes generically for nested tool input, while agent_tool_invocation exposes metadata for the current nested invocation directly on the result.

Streaming lifecycle and diagnostics

RunResultStreaming inherits the same result surfaces above, but adds streaming-specific controls:

Keep 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

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 is just the ID from the last entry in raw_responses.

Each ModelResponse also exposes two diagnostics that apply to that individual model call:

  • 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 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.

Guardrail results

Agent-level guardrails are exposed as input_guardrail_results and output_guardrail_results.

Tool guardrails are exposed separately as tool_input_guardrail_results and 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.

One redaction rule applies when an agent-level output guardrail blocks final output produced directly by a terminal function tool. For the blocked current response, output_guardrail_results replaces the rejected agent output and clears payload-bearing output metadata, while tool_output_guardrail_results replaces payload-bearing tool metadata. Earlier accepted results remain unchanged. The sanitized output-guardrail result is exposed as guardrail_result on OutputGuardrailTripwireTriggered. Sanitized output-guardrail and tool-output-guardrail results are also exposed through streamed result state and RunState; see Output guardrails.

Context and usage

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.