Skip to content

Results

AgentToolInvocation dataclass

Immutable metadata about a nested agent-tool invocation.

Source code in src/agents/result.py
@dataclass(frozen=True)
class AgentToolInvocation:
    """Immutable metadata about a nested agent-tool invocation."""

    tool_name: str
    """The nested tool name exposed to the model."""

    tool_call_id: str
    """The tool call ID for the nested invocation."""

    tool_arguments: str
    """The raw JSON arguments for the nested invocation."""

tool_name instance-attribute

tool_name: str

The nested tool name exposed to the model.

tool_call_id instance-attribute

tool_call_id: str

The tool call ID for the nested invocation.

tool_arguments instance-attribute

tool_arguments: str

The raw JSON arguments for the nested invocation.

RunResultBase dataclass

Bases: ABC

Source code in src/agents/result.py
@dataclass
class RunResultBase(abc.ABC):
    input: str | list[TResponseInputItem]
    """The original input items i.e. the items before run() was called. This may be a mutated
    version of the input, if there are handoff input filters that mutate the input.
    """

    new_items: list[RunItem]
    """The new items generated during the agent run. These include things like new messages, tool
    calls and their outputs, etc.
    """

    raw_responses: list[ModelResponse]
    """The raw LLM responses generated by the model during the agent run."""

    final_output: Any
    """The output of the last agent."""

    input_guardrail_results: list[InputGuardrailResult]
    """Guardrail results for the input messages."""

    output_guardrail_results: list[OutputGuardrailResult]
    """Guardrail results for the final output of the agent."""

    tool_input_guardrail_results: list[ToolInputGuardrailResult]
    """Tool input guardrail results from all tools executed during the run."""

    tool_output_guardrail_results: list[ToolOutputGuardrailResult]
    """Tool output guardrail results from all tools executed during the run."""

    context_wrapper: RunContextWrapper[Any]
    """The context wrapper for the agent run."""

    _trace_state: TraceState | None = field(default=None, init=False, repr=False)
    """Serialized trace metadata captured during the run."""
    _replay_from_model_input_items: bool = field(default=False, init=False, repr=False)
    """Whether replay helpers should prefer `_model_input_items` over `new_items`.

    This is only set when the runner preserved extra session history items that should not be
    replayed into the next local run, such as nested handoff history or filtered handoff input.
    """
    _nested_history_owned_session_item_refs: list[NestedHistoryOwnedItemRef] = field(
        default_factory=list,
        init=False,
        repr=False,
    )
    """Session item occurrences already represented verbatim in SDK-default nested history."""
    _sandbox_resume_state: dict[str, object] | None = field(default=None, init=False, repr=False)
    """Serialized sandbox session state captured during the run."""
    _sandbox_session: BaseSandboxSession | None = field(default=None, init=False, repr=False)
    """Live sandbox session attached to this run result when sandbox execution is enabled."""
    _starting_agent_for_state: Agent[Any] | None = field(default=None, init=False, repr=False)
    """Root agent graph used when converting the result back into RunState."""
    _generated_prompt_cache_key: str | None = field(default=None, init=False, repr=False)
    """SDK-generated prompt cache key captured during the run."""

    @classmethod
    def __get_pydantic_core_schema__(
        cls,
        _source_type: Any,
        _handler: GetCoreSchemaHandler,
    ) -> core_schema.CoreSchema:
        # RunResult objects are runtime values; schema generation should treat them as instances
        # instead of recursively traversing internal dataclass annotations.
        return core_schema.is_instance_schema(cls)

    @property
    @abc.abstractmethod
    def last_agent(self) -> Agent[Any]:
        """The last agent that was run."""

    def release_agents(self, *, release_new_items: bool = True) -> None:
        """
        Release strong references to agents held by this result. After calling this method,
        accessing `item.agent` or `last_agent` may return `None` if the agent has been garbage
        collected. Callers can use this when they are done inspecting the result and want to
        eagerly drop any associated agent graph.
        """
        if release_new_items:
            for item in self.new_items:
                release = getattr(item, "release_agent", None)
                if callable(release):
                    release()
        self._release_last_agent_reference()

    def __del__(self) -> None:
        try:
            # Fall back to releasing agents automatically in case the caller never invoked
            # `release_agents()` explicitly so GC of the RunResult drops the last strong reference.
            # We pass `release_new_items=False` so RunItems that the user intentionally keeps
            # continue exposing their originating agent until that agent itself is collected.
            self.release_agents(release_new_items=False)
        except Exception:
            # Avoid raising from __del__.
            pass

    @abc.abstractmethod
    def _release_last_agent_reference(self) -> None:
        """Release stored agent reference specific to the concrete result type."""

    def final_output_as(self, cls: type[T], raise_if_incorrect_type: bool = False) -> T:
        """A convenience method to cast the final output to a specific type. By default, the cast
        is only for the typechecker. If you set `raise_if_incorrect_type` to True, we'll raise a
        TypeError if the final output is not of the given type.

        Args:
            cls: The type to cast the final output to.
            raise_if_incorrect_type: If True, we'll raise a TypeError if the final output is not of
                the given type.

        Returns:
            The final output casted to the given type.
        """
        if raise_if_incorrect_type and not isinstance(self.final_output, cls):
            raise TypeError(f"Final output is not of type {cls.__name__}")

        return cast(T, self.final_output)

    def to_input_list(
        self,
        *,
        mode: ToInputListMode = "preserve_all",
    ) -> list[TResponseInputItem]:
        """Create an input-item view of this run.

        ``mode="preserve_all"`` keeps the historical behavior of converting ``new_items`` into a
        full plain-item history. ``mode="normalized"`` prefers the canonical continuation input
        when handoff filtering rewrote model history, while remaining identical for ordinary runs.
        """
        public_input = self.input
        owned_item_refs = _reconciled_result_owned_item_refs(self, public_input)
        original_items = ItemHelpers.input_to_new_input_list(public_input)
        reasoning_item_id_policy = getattr(self, "_reasoning_item_id_policy", None)
        replay_items = _input_items_for_result(
            self,
            mode=mode,
            reasoning_item_id_policy=reasoning_item_id_policy,
            public_input=public_input,
            owned_item_refs=owned_item_refs,
        )
        return original_items + replay_items

    @property
    def agent_tool_invocation(self) -> AgentToolInvocation | None:
        """Immutable metadata for results produced by `Agent.as_tool()`.

        Returns `None` for ordinary top-level runs.
        """
        from .tool_context import ToolContext

        if not isinstance(self.context_wrapper, ToolContext):
            return None

        return AgentToolInvocation(
            tool_name=self.context_wrapper.tool_name,
            tool_call_id=self.context_wrapper.tool_call_id,
            tool_arguments=self.context_wrapper.tool_arguments,
        )

    @property
    def last_response_id(self) -> str | None:
        """Convenience method to get the response ID of the last model response."""
        if not self.raw_responses:
            return None

        return self.raw_responses[-1].response_id

input instance-attribute

input: str | list[TResponseInputItem]

The original input items i.e. the items before run() was called. This may be a mutated version of the input, if there are handoff input filters that mutate the input.

new_items instance-attribute

new_items: list[RunItem]

The new items generated during the agent run. These include things like new messages, tool calls and their outputs, etc.

raw_responses instance-attribute

raw_responses: list[ModelResponse]

The raw LLM responses generated by the model during the agent run.

final_output instance-attribute

final_output: Any

The output of the last agent.

input_guardrail_results instance-attribute

input_guardrail_results: list[InputGuardrailResult]

Guardrail results for the input messages.

output_guardrail_results instance-attribute

output_guardrail_results: list[OutputGuardrailResult]

Guardrail results for the final output of the agent.

tool_input_guardrail_results instance-attribute

tool_input_guardrail_results: list[ToolInputGuardrailResult]

Tool input guardrail results from all tools executed during the run.

tool_output_guardrail_results instance-attribute

tool_output_guardrail_results: list[
    ToolOutputGuardrailResult
]

Tool output guardrail results from all tools executed during the run.

context_wrapper instance-attribute

context_wrapper: RunContextWrapper[Any]

The context wrapper for the agent run.

last_agent abstractmethod property

last_agent: Agent[Any]

The last agent that was run.

agent_tool_invocation property

agent_tool_invocation: AgentToolInvocation | None

Immutable metadata for results produced by Agent.as_tool().

Returns None for ordinary top-level runs.

last_response_id property

last_response_id: str | None

Convenience method to get the response ID of the last model response.

release_agents

release_agents(*, release_new_items: bool = True) -> None

Release strong references to agents held by this result. After calling this method, accessing item.agent or last_agent may return None if the agent has been garbage collected. Callers can use this when they are done inspecting the result and want to eagerly drop any associated agent graph.

Source code in src/agents/result.py
def release_agents(self, *, release_new_items: bool = True) -> None:
    """
    Release strong references to agents held by this result. After calling this method,
    accessing `item.agent` or `last_agent` may return `None` if the agent has been garbage
    collected. Callers can use this when they are done inspecting the result and want to
    eagerly drop any associated agent graph.
    """
    if release_new_items:
        for item in self.new_items:
            release = getattr(item, "release_agent", None)
            if callable(release):
                release()
    self._release_last_agent_reference()

final_output_as

final_output_as(
    cls: type[T], raise_if_incorrect_type: bool = False
) -> T

A convenience method to cast the final output to a specific type. By default, the cast is only for the typechecker. If you set raise_if_incorrect_type to True, we'll raise a TypeError if the final output is not of the given type.

Parameters:

Name Type Description Default
cls type[T]

The type to cast the final output to.

required
raise_if_incorrect_type bool

If True, we'll raise a TypeError if the final output is not of the given type.

False

Returns:

Type Description
T

The final output casted to the given type.

Source code in src/agents/result.py
def final_output_as(self, cls: type[T], raise_if_incorrect_type: bool = False) -> T:
    """A convenience method to cast the final output to a specific type. By default, the cast
    is only for the typechecker. If you set `raise_if_incorrect_type` to True, we'll raise a
    TypeError if the final output is not of the given type.

    Args:
        cls: The type to cast the final output to.
        raise_if_incorrect_type: If True, we'll raise a TypeError if the final output is not of
            the given type.

    Returns:
        The final output casted to the given type.
    """
    if raise_if_incorrect_type and not isinstance(self.final_output, cls):
        raise TypeError(f"Final output is not of type {cls.__name__}")

    return cast(T, self.final_output)

to_input_list

to_input_list(
    *, mode: ToInputListMode = "preserve_all"
) -> list[TResponseInputItem]

Create an input-item view of this run.

mode="preserve_all" keeps the historical behavior of converting new_items into a full plain-item history. mode="normalized" prefers the canonical continuation input when handoff filtering rewrote model history, while remaining identical for ordinary runs.

Source code in src/agents/result.py
def to_input_list(
    self,
    *,
    mode: ToInputListMode = "preserve_all",
) -> list[TResponseInputItem]:
    """Create an input-item view of this run.

    ``mode="preserve_all"`` keeps the historical behavior of converting ``new_items`` into a
    full plain-item history. ``mode="normalized"`` prefers the canonical continuation input
    when handoff filtering rewrote model history, while remaining identical for ordinary runs.
    """
    public_input = self.input
    owned_item_refs = _reconciled_result_owned_item_refs(self, public_input)
    original_items = ItemHelpers.input_to_new_input_list(public_input)
    reasoning_item_id_policy = getattr(self, "_reasoning_item_id_policy", None)
    replay_items = _input_items_for_result(
        self,
        mode=mode,
        reasoning_item_id_policy=reasoning_item_id_policy,
        public_input=public_input,
        owned_item_refs=owned_item_refs,
    )
    return original_items + replay_items

RunResult dataclass

Bases: RunResultBase

Source code in src/agents/result.py
@dataclass
class RunResult(RunResultBase):
    _last_agent: Agent[Any]
    _last_agent_ref: weakref.ReferenceType[Agent[Any]] | None = field(
        init=False,
        repr=False,
        default=None,
    )
    _last_processed_response: ProcessedResponse | None = field(default=None, repr=False)
    """The last processed model response. This is needed for resuming from interruptions."""
    _tool_use_tracker_snapshot: dict[str, list[str]] = field(default_factory=dict, repr=False)
    _current_turn_persisted_item_count: int = 0
    """Number of items from new_items already persisted to session for the
    current turn."""
    _current_turn: int = 0
    """The current turn number. This is preserved when converting to RunState."""
    _model_input_items: list[RunItem] = field(default_factory=list, repr=False)
    """Filtered items used to build model input when resuming runs."""
    _original_input: str | list[TResponseInputItem] | None = field(default=None, repr=False)
    """The original input for the current run segment.
    This is updated when handoffs or resume logic replace the input history, and used by to_state()
    to preserve the correct originalInput when serializing state."""
    _conversation_id: str | None = field(default=None, repr=False)
    """Conversation identifier for server-managed runs."""
    _previous_response_id: str | None = field(default=None, repr=False)
    """Response identifier returned by the server for the last turn."""
    _auto_previous_response_id: bool = field(default=False, repr=False)
    """Whether automatic previous response tracking was enabled."""
    _reasoning_item_id_policy: Literal["preserve", "omit"] | None = field(
        default=None, init=False, repr=False
    )
    """How reasoning IDs should be represented when converting to input history."""
    max_turns: int | None = 10
    """The maximum number of turns allowed for this run, or ``None`` for no limit."""
    interruptions: list[ToolApprovalItem] = field(default_factory=list)
    """Pending tool approval requests (interruptions) for this run."""

    def __post_init__(self) -> None:
        self._last_agent_ref = weakref.ref(self._last_agent)

    @property
    def last_agent(self) -> Agent[Any]:
        """The last agent that was run."""
        agent = cast("Agent[Any] | None", self.__dict__.get("_last_agent"))
        if agent is not None:
            return agent
        if self._last_agent_ref:
            agent = self._last_agent_ref()
            if agent is not None:
                return agent
        raise AgentsException("Last agent reference is no longer available.")

    def _release_last_agent_reference(self) -> None:
        agent = cast("Agent[Any] | None", self.__dict__.get("_last_agent"))
        if agent is None:
            return
        self._last_agent_ref = weakref.ref(agent)
        # Preserve dataclass field so repr/asdict continue to succeed.
        self.__dict__["_last_agent"] = None

    def to_state(self) -> RunState[Any]:
        """Create a RunState from this result to resume execution.

        This is useful when the run was interrupted (e.g., for tool approval). You can
        approve or reject the tool calls on the returned state, then pass it back to
        `Runner.run()` to continue execution.

        Returns:
            A RunState that can be used to resume the run.

        Example:
            ```python
            # Run agent until it needs approval
            result = await Runner.run(agent, "Use the delete_file tool")

            if result.interruptions:
                # Approve the tool call
                state = result.to_state()
                state.approve(result.interruptions[0])

                # Resume the run
                result = await Runner.run(agent, state)
            ```
        """
        # Create a RunState from the current result
        original_input_for_state = getattr(self, "_original_input", None)
        state = RunState(
            context=self.context_wrapper,
            original_input=original_input_for_state
            if original_input_for_state is not None
            else self.input,
            starting_agent=_starting_agent_for_state(self),
            max_turns=self.max_turns,
        )

        return _populate_state_from_result(
            state,
            self,
            current_turn=self._current_turn,
            last_processed_response=self._last_processed_response,
            current_turn_persisted_item_count=self._current_turn_persisted_item_count,
            tool_use_tracker_snapshot=self._tool_use_tracker_snapshot,
            conversation_id=self._conversation_id,
            previous_response_id=self._previous_response_id,
            auto_previous_response_id=self._auto_previous_response_id,
        )

    def __str__(self) -> str:
        return pretty_print_result(self)

max_turns class-attribute instance-attribute

max_turns: int | None = 10

The maximum number of turns allowed for this run, or None for no limit.

interruptions class-attribute instance-attribute

interruptions: list[ToolApprovalItem] = field(
    default_factory=list
)

Pending tool approval requests (interruptions) for this run.

last_agent property

last_agent: Agent[Any]

The last agent that was run.

input instance-attribute

input: str | list[TResponseInputItem]

The original input items i.e. the items before run() was called. This may be a mutated version of the input, if there are handoff input filters that mutate the input.

new_items instance-attribute

new_items: list[RunItem]

The new items generated during the agent run. These include things like new messages, tool calls and their outputs, etc.

raw_responses instance-attribute

raw_responses: list[ModelResponse]

The raw LLM responses generated by the model during the agent run.

final_output instance-attribute

final_output: Any

The output of the last agent.

input_guardrail_results instance-attribute

input_guardrail_results: list[InputGuardrailResult]

Guardrail results for the input messages.

output_guardrail_results instance-attribute

output_guardrail_results: list[OutputGuardrailResult]

Guardrail results for the final output of the agent.

tool_input_guardrail_results instance-attribute

tool_input_guardrail_results: list[ToolInputGuardrailResult]

Tool input guardrail results from all tools executed during the run.

tool_output_guardrail_results instance-attribute

tool_output_guardrail_results: list[
    ToolOutputGuardrailResult
]

Tool output guardrail results from all tools executed during the run.

context_wrapper instance-attribute

context_wrapper: RunContextWrapper[Any]

The context wrapper for the agent run.

agent_tool_invocation property

agent_tool_invocation: AgentToolInvocation | None

Immutable metadata for results produced by Agent.as_tool().

Returns None for ordinary top-level runs.

last_response_id property

last_response_id: str | None

Convenience method to get the response ID of the last model response.

to_state

to_state() -> RunState[Any]

Create a RunState from this result to resume execution.

This is useful when the run was interrupted (e.g., for tool approval). You can approve or reject the tool calls on the returned state, then pass it back to Runner.run() to continue execution.

Returns:

Type Description
RunState[Any]

A RunState that can be used to resume the run.

Example
# Run agent until it needs approval
result = await Runner.run(agent, "Use the delete_file tool")

if result.interruptions:
    # Approve the tool call
    state = result.to_state()
    state.approve(result.interruptions[0])

    # Resume the run
    result = await Runner.run(agent, state)
Source code in src/agents/result.py
def to_state(self) -> RunState[Any]:
    """Create a RunState from this result to resume execution.

    This is useful when the run was interrupted (e.g., for tool approval). You can
    approve or reject the tool calls on the returned state, then pass it back to
    `Runner.run()` to continue execution.

    Returns:
        A RunState that can be used to resume the run.

    Example:
        ```python
        # Run agent until it needs approval
        result = await Runner.run(agent, "Use the delete_file tool")

        if result.interruptions:
            # Approve the tool call
            state = result.to_state()
            state.approve(result.interruptions[0])

            # Resume the run
            result = await Runner.run(agent, state)
        ```
    """
    # Create a RunState from the current result
    original_input_for_state = getattr(self, "_original_input", None)
    state = RunState(
        context=self.context_wrapper,
        original_input=original_input_for_state
        if original_input_for_state is not None
        else self.input,
        starting_agent=_starting_agent_for_state(self),
        max_turns=self.max_turns,
    )

    return _populate_state_from_result(
        state,
        self,
        current_turn=self._current_turn,
        last_processed_response=self._last_processed_response,
        current_turn_persisted_item_count=self._current_turn_persisted_item_count,
        tool_use_tracker_snapshot=self._tool_use_tracker_snapshot,
        conversation_id=self._conversation_id,
        previous_response_id=self._previous_response_id,
        auto_previous_response_id=self._auto_previous_response_id,
    )

release_agents

release_agents(*, release_new_items: bool = True) -> None

Release strong references to agents held by this result. After calling this method, accessing item.agent or last_agent may return None if the agent has been garbage collected. Callers can use this when they are done inspecting the result and want to eagerly drop any associated agent graph.

Source code in src/agents/result.py
def release_agents(self, *, release_new_items: bool = True) -> None:
    """
    Release strong references to agents held by this result. After calling this method,
    accessing `item.agent` or `last_agent` may return `None` if the agent has been garbage
    collected. Callers can use this when they are done inspecting the result and want to
    eagerly drop any associated agent graph.
    """
    if release_new_items:
        for item in self.new_items:
            release = getattr(item, "release_agent", None)
            if callable(release):
                release()
    self._release_last_agent_reference()

final_output_as

final_output_as(
    cls: type[T], raise_if_incorrect_type: bool = False
) -> T

A convenience method to cast the final output to a specific type. By default, the cast is only for the typechecker. If you set raise_if_incorrect_type to True, we'll raise a TypeError if the final output is not of the given type.

Parameters:

Name Type Description Default
cls type[T]

The type to cast the final output to.

required
raise_if_incorrect_type bool

If True, we'll raise a TypeError if the final output is not of the given type.

False

Returns:

Type Description
T

The final output casted to the given type.

Source code in src/agents/result.py
def final_output_as(self, cls: type[T], raise_if_incorrect_type: bool = False) -> T:
    """A convenience method to cast the final output to a specific type. By default, the cast
    is only for the typechecker. If you set `raise_if_incorrect_type` to True, we'll raise a
    TypeError if the final output is not of the given type.

    Args:
        cls: The type to cast the final output to.
        raise_if_incorrect_type: If True, we'll raise a TypeError if the final output is not of
            the given type.

    Returns:
        The final output casted to the given type.
    """
    if raise_if_incorrect_type and not isinstance(self.final_output, cls):
        raise TypeError(f"Final output is not of type {cls.__name__}")

    return cast(T, self.final_output)

to_input_list

to_input_list(
    *, mode: ToInputListMode = "preserve_all"
) -> list[TResponseInputItem]

Create an input-item view of this run.

mode="preserve_all" keeps the historical behavior of converting new_items into a full plain-item history. mode="normalized" prefers the canonical continuation input when handoff filtering rewrote model history, while remaining identical for ordinary runs.

Source code in src/agents/result.py
def to_input_list(
    self,
    *,
    mode: ToInputListMode = "preserve_all",
) -> list[TResponseInputItem]:
    """Create an input-item view of this run.

    ``mode="preserve_all"`` keeps the historical behavior of converting ``new_items`` into a
    full plain-item history. ``mode="normalized"`` prefers the canonical continuation input
    when handoff filtering rewrote model history, while remaining identical for ordinary runs.
    """
    public_input = self.input
    owned_item_refs = _reconciled_result_owned_item_refs(self, public_input)
    original_items = ItemHelpers.input_to_new_input_list(public_input)
    reasoning_item_id_policy = getattr(self, "_reasoning_item_id_policy", None)
    replay_items = _input_items_for_result(
        self,
        mode=mode,
        reasoning_item_id_policy=reasoning_item_id_policy,
        public_input=public_input,
        owned_item_refs=owned_item_refs,
    )
    return original_items + replay_items

RunResultStreaming dataclass

Bases: RunResultBase

The result of an agent run in streaming mode. You can use the stream_events method to receive semantic events as they are generated.

The streaming method will raise: - A MaxTurnsExceeded exception if the agent exceeds the max_turns limit. - A GuardrailTripwireTriggered exception if a guardrail is tripped.

Source code in src/agents/result.py
 524
 525
 526
 527
 528
 529
 530
 531
 532
 533
 534
 535
 536
 537
 538
 539
 540
 541
 542
 543
 544
 545
 546
 547
 548
 549
 550
 551
 552
 553
 554
 555
 556
 557
 558
 559
 560
 561
 562
 563
 564
 565
 566
 567
 568
 569
 570
 571
 572
 573
 574
 575
 576
 577
 578
 579
 580
 581
 582
 583
 584
 585
 586
 587
 588
 589
 590
 591
 592
 593
 594
 595
 596
 597
 598
 599
 600
 601
 602
 603
 604
 605
 606
 607
 608
 609
 610
 611
 612
 613
 614
 615
 616
 617
 618
 619
 620
 621
 622
 623
 624
 625
 626
 627
 628
 629
 630
 631
 632
 633
 634
 635
 636
 637
 638
 639
 640
 641
 642
 643
 644
 645
 646
 647
 648
 649
 650
 651
 652
 653
 654
 655
 656
 657
 658
 659
 660
 661
 662
 663
 664
 665
 666
 667
 668
 669
 670
 671
 672
 673
 674
 675
 676
 677
 678
 679
 680
 681
 682
 683
 684
 685
 686
 687
 688
 689
 690
 691
 692
 693
 694
 695
 696
 697
 698
 699
 700
 701
 702
 703
 704
 705
 706
 707
 708
 709
 710
 711
 712
 713
 714
 715
 716
 717
 718
 719
 720
 721
 722
 723
 724
 725
 726
 727
 728
 729
 730
 731
 732
 733
 734
 735
 736
 737
 738
 739
 740
 741
 742
 743
 744
 745
 746
 747
 748
 749
 750
 751
 752
 753
 754
 755
 756
 757
 758
 759
 760
 761
 762
 763
 764
 765
 766
 767
 768
 769
 770
 771
 772
 773
 774
 775
 776
 777
 778
 779
 780
 781
 782
 783
 784
 785
 786
 787
 788
 789
 790
 791
 792
 793
 794
 795
 796
 797
 798
 799
 800
 801
 802
 803
 804
 805
 806
 807
 808
 809
 810
 811
 812
 813
 814
 815
 816
 817
 818
 819
 820
 821
 822
 823
 824
 825
 826
 827
 828
 829
 830
 831
 832
 833
 834
 835
 836
 837
 838
 839
 840
 841
 842
 843
 844
 845
 846
 847
 848
 849
 850
 851
 852
 853
 854
 855
 856
 857
 858
 859
 860
 861
 862
 863
 864
 865
 866
 867
 868
 869
 870
 871
 872
 873
 874
 875
 876
 877
 878
 879
 880
 881
 882
 883
 884
 885
 886
 887
 888
 889
 890
 891
 892
 893
 894
 895
 896
 897
 898
 899
 900
 901
 902
 903
 904
 905
 906
 907
 908
 909
 910
 911
 912
 913
 914
 915
 916
 917
 918
 919
 920
 921
 922
 923
 924
 925
 926
 927
 928
 929
 930
 931
 932
 933
 934
 935
 936
 937
 938
 939
 940
 941
 942
 943
 944
 945
 946
 947
 948
 949
 950
 951
 952
 953
 954
 955
 956
 957
 958
 959
 960
 961
 962
 963
 964
 965
 966
 967
 968
 969
 970
 971
 972
 973
 974
 975
 976
 977
 978
 979
 980
 981
 982
 983
 984
 985
 986
 987
 988
 989
 990
 991
 992
 993
 994
 995
 996
 997
 998
 999
1000
1001
1002
1003
1004
1005
1006
1007
1008
1009
1010
1011
1012
1013
1014
1015
1016
@dataclass
class RunResultStreaming(RunResultBase):
    """The result of an agent run in streaming mode. You can use the `stream_events` method to
    receive semantic events as they are generated.

    The streaming method will raise:
    - A MaxTurnsExceeded exception if the agent exceeds the max_turns limit.
    - A GuardrailTripwireTriggered exception if a guardrail is tripped.
    """

    current_agent: Agent[Any]
    """The current agent that is running."""

    current_turn: int
    """The current turn number."""

    max_turns: int | None
    """The maximum number of turns the agent can run for, or ``None`` for no limit."""

    final_output: Any
    """The final output of the agent. This is None until the agent has finished running."""

    _current_agent_output_schema: AgentOutputSchemaBase | None = field(repr=False)

    trace: Trace | None = field(repr=False)

    is_complete: bool = False
    """Whether the agent has finished running."""

    _current_agent_ref: weakref.ReferenceType[Agent[Any]] | None = field(
        init=False,
        repr=False,
        default=None,
    )

    _model_input_items: list[RunItem] = field(default_factory=list, repr=False)
    """Filtered items used to build model input between streaming turns."""

    # Queues that the background run_loop writes to
    _event_queue: asyncio.Queue[StreamEvent | QueueCompleteSentinel] = field(
        default_factory=asyncio.Queue, repr=False
    )
    _input_guardrail_queue: asyncio.Queue[InputGuardrailResult] = field(
        default_factory=asyncio.Queue, repr=False
    )

    # Store the asyncio tasks that we're waiting on
    run_loop_task: asyncio.Task[Any] | None = field(default=None, repr=False)
    _input_guardrails_task: asyncio.Task[Any] | None = field(default=None, repr=False)
    _triggered_input_guardrail_result: InputGuardrailResult | None = field(default=None, repr=False)
    _output_guardrails_task: asyncio.Task[Any] | None = field(default=None, repr=False)
    _stored_exception: Exception | None = field(default=None, repr=False)
    _cancel_mode: Literal["none", "immediate", "after_turn"] = field(default="none", repr=False)
    _last_processed_response: ProcessedResponse | None = field(default=None, repr=False)
    """The last processed model response. This is needed for resuming from interruptions."""
    interruptions: list[ToolApprovalItem] = field(default_factory=list)
    """Pending tool approval requests (interruptions) for this run."""
    _waiting_on_event_queue: bool = field(default=False, repr=False)

    _current_turn_persisted_item_count: int = 0
    """Number of items from new_items already persisted to session for the
    current turn."""

    _stream_input_persisted: bool = False
    """Whether the input has been persisted to the session. Prevents double-saving."""

    _original_input_for_persistence: list[TResponseInputItem] | None = None
    """Original turn input before session history was merged, used for
    persistence (matches JS sessionInputOriginalSnapshot)."""

    _max_turns_handled: bool = field(default=False, repr=False)

    _original_input: str | list[TResponseInputItem] | None = field(default=None, repr=False)
    """The original input from the first turn. Unlike `input`, this is never updated during the run.
    Used by to_state() to preserve the correct originalInput when serializing state."""
    _tool_use_tracker_snapshot: dict[str, list[str]] = field(default_factory=dict, repr=False)
    _state: Any = field(default=None, repr=False)
    """Internal reference to the RunState for streaming results."""
    _conversation_id: str | None = field(default=None, repr=False)
    """Conversation identifier for server-managed runs."""
    _previous_response_id: str | None = field(default=None, repr=False)
    """Response identifier returned by the server for the last turn."""
    _auto_previous_response_id: bool = field(default=False, repr=False)
    """Whether automatic previous response tracking was enabled."""
    _reasoning_item_id_policy: Literal["preserve", "omit"] | None = field(
        default=None, init=False, repr=False
    )
    """How reasoning IDs should be represented when converting to input history."""
    _run_impl_task: InitVar[asyncio.Task[Any] | None] = None
    _sandbox_cleanup: Callable[[], Awaitable[None]] | None = field(
        default=None,
        init=False,
        repr=False,
    )
    _sandbox_cleanup_task: asyncio.Task[None] | None = field(default=None, init=False, repr=False)
    _sandbox_cleanup_callback_registered: bool = field(default=False, init=False, repr=False)

    def __post_init__(self, _run_impl_task: asyncio.Task[Any] | None) -> None:
        self._current_agent_ref = weakref.ref(self.current_agent)
        # Store the original input at creation time (it will be set via input field)
        if self._original_input is None:
            self._original_input = self.input
        # Compatibility shim: accept legacy `_run_impl_task` constructor keyword.
        if self.run_loop_task is None and _run_impl_task is not None:
            self.run_loop_task = _run_impl_task

    @property
    def last_agent(self) -> Agent[Any]:
        """The last agent that was run. Updates as the agent run progresses, so the true last agent
        is only available after the agent run is complete.
        """
        agent = cast("Agent[Any] | None", self.__dict__.get("current_agent"))
        if agent is not None:
            return agent
        if self._current_agent_ref:
            agent = self._current_agent_ref()
            if agent is not None:
                return agent
        raise AgentsException("Last agent reference is no longer available.")

    def _release_last_agent_reference(self) -> None:
        agent = cast("Agent[Any] | None", self.__dict__.get("current_agent"))
        if agent is None:
            return
        self._current_agent_ref = weakref.ref(agent)
        # Preserve dataclass field so repr/asdict continue to succeed.
        self.__dict__["current_agent"] = None

    async def _run_sandbox_cleanup(self) -> None:
        sandbox_cleanup = self._sandbox_cleanup
        if sandbox_cleanup is None:
            return

        task = self._sandbox_cleanup_task
        if task is None:

            async def _cleanup_once() -> None:
                try:
                    await sandbox_cleanup()
                except Exception as error:
                    logger.warning(
                        "Failed to clean up sandbox resources after streamed run: %s", error
                    )

            task = asyncio.create_task(_cleanup_once())
            self._sandbox_cleanup_task = task

        await task

    def ensure_sandbox_cleanup_on_completion(self) -> None:
        if (
            self._sandbox_cleanup is None
            or self.run_loop_task is None
            or self._sandbox_cleanup_callback_registered
        ):
            return

        original_task = self.run_loop_task
        self._sandbox_cleanup_callback_registered = True
        original_task.add_done_callback(
            lambda _task: asyncio.create_task(self._run_sandbox_cleanup())
        )

        async def _await_run_and_cleanup() -> Any:
            try:
                result = await original_task
            except asyncio.CancelledError:
                if not original_task.done():
                    original_task.cancel()
                raise
            except Exception:
                await self._run_sandbox_cleanup()
                raise

            await self._run_sandbox_cleanup()
            return result

        self.run_loop_task = asyncio.create_task(_await_run_and_cleanup())

    @property
    def run_loop_exception(self) -> BaseException | None:
        """The exception raised by the background run loop, if any.

        When the run loop fails before producing stream events (for example during early
        sandbox initialisation), the exception may not be re-raised through
        :meth:`stream_events`. This property gives callers a reliable way to check for
        silent failures after consuming the stream:

        .. code-block:: python

            result = Runner.run_streamed(agent, "hello")
            async for event in result.stream_events():
                pass
            if result.run_loop_exception:
                raise result.run_loop_exception

        Returns ``None`` if the run loop completed without error, has not yet finished,
        or was cancelled.
        """
        task = self.run_loop_task
        if task is None or not task.done() or task.cancelled():
            return None
        return task.exception()

    def cancel(self, mode: Literal["immediate", "after_turn"] = "immediate") -> None:
        """Cancel the streaming run.

        Args:
            mode: Cancellation strategy:
                - "immediate": Stop immediately, cancel all tasks, clear queues (default)
                - "after_turn": Complete current turn gracefully before stopping
                    * Allows LLM response to finish
                    * Executes pending tool calls
                    * Saves session state properly
                    * Tracks usage accurately
                    * Stops before next turn begins

        Example:
            ```python
            result = Runner.run_streamed(agent, "Task", session=session)

            async for event in result.stream_events():
                if user_interrupted():
                    result.cancel(mode="after_turn")  # Graceful
                    # result.cancel()  # Immediate (default)
            ```

        Note: After calling cancel(), you should continue consuming stream_events()
        to allow the cancellation to complete properly.
        """
        # Store the cancel mode for the background task to check
        self._cancel_mode = mode

        if mode == "immediate":
            # Existing behavior - immediate shutdown
            self._cleanup_tasks()  # Cancel all running tasks
            self.is_complete = True  # Mark the run as complete to stop event streaming

            while not self._input_guardrail_queue.empty():
                self._input_guardrail_queue.get_nowait()

            # Unblock any streamers waiting on the event queue.
            self._event_queue.put_nowait(QueueCompleteSentinel())
            if not self._waiting_on_event_queue:
                self._drain_event_queue()

        elif mode == "after_turn":
            # Soft cancel - just set the flag
            # The streaming loop will check this and stop gracefully
            # Don't call _cleanup_tasks() or clear queues yet
            pass

    async def stream_events(self) -> AsyncIterator[StreamEvent]:
        """Stream deltas for new items as they are generated. We're using the types from the
        OpenAI Responses API, so these are semantic events: each event has a `type` field that
        describes the type of the event, along with the data for that event.

        This will raise:
        - A MaxTurnsExceeded exception if the agent exceeds the max_turns limit.
        - A GuardrailTripwireTriggered exception if a guardrail is tripped.
        """
        cancelled = False
        try:
            while True:
                self._check_errors()
                should_drain_queued_events = isinstance(
                    self._stored_exception, MaxTurnsExceeded
                ) or (
                    self._stored_exception is not None
                    and _should_drain_stream_events_before_raising(self._stored_exception)
                )
                if self._stored_exception and (
                    not should_drain_queued_events or self._event_queue.empty()
                ):
                    logger.debug("Breaking due to stored exception")
                    self.is_complete = True
                    break

                if self.is_complete and self._event_queue.empty():
                    break

                try:
                    self._waiting_on_event_queue = True
                    item = await self._event_queue.get()
                except asyncio.CancelledError:
                    cancelled = True
                    self.cancel()
                    raise
                finally:
                    self._waiting_on_event_queue = False

                if isinstance(item, QueueCompleteSentinel):
                    # Await input guardrails if they are still running, so late
                    # exceptions are captured.
                    await self._await_task_safely(self._input_guardrails_task)

                    self._event_queue.task_done()

                    # Check for errors, in case the queue was completed
                    # due to an exception
                    self._check_errors()
                    break

                yield item
                self._event_queue.task_done()
        finally:
            try:
                if cancelled:
                    # Cancellation should return promptly, so avoid waiting on long-running tasks.
                    # Tasks have already been cancelled above.
                    self._cleanup_tasks()
                else:
                    # Ensure main execution completes before cleanup to avoid race conditions
                    # with session operations.
                    await self._await_task_safely(self.run_loop_task)
                    # Re-check for exceptions now that the run loop has fully settled.
                    # _await_task_safely swallows exceptions; without this call, a run-loop
                    # failure that races past the sentinel (e.g. early sandbox failures) would
                    # be silently lost instead of surfaced via _stored_exception.
                    self._check_errors()
                    # Safely terminate all background tasks after main execution has finished.
                    self._cleanup_tasks()

                if not cancelled:
                    await self._run_sandbox_cleanup()
            finally:
                # Allow any pending callbacks (e.g., cancellation handlers) to enqueue their
                # completion sentinels before we clear the queues for observability.
                await asyncio.sleep(0)

                # Drain queues so callers observing internal state see them empty after completion.
                self._drain_event_queue()
                self._drain_input_guardrail_queue()

        if self._stored_exception:
            raise self._stored_exception

    def _create_error_details(self) -> RunErrorDetails:
        """Return a `RunErrorDetails` object considering the current attributes of the class."""
        return RunErrorDetails(
            input=self.input,
            new_items=self.new_items,
            raw_responses=self.raw_responses,
            last_agent=self.current_agent,
            context_wrapper=self.context_wrapper,
            input_guardrail_results=self.input_guardrail_results,
            output_guardrail_results=self.output_guardrail_results,
        )

    def _check_errors(self):
        if (
            self.max_turns is not None
            and self.current_turn > self.max_turns
            and not self._max_turns_handled
        ):
            max_turns_exc = MaxTurnsExceeded(f"Max turns ({self.max_turns}) exceeded")
            max_turns_exc.run_data = self._create_error_details()
            self._stored_exception = max_turns_exc

        # Fetch all the completed guardrail results from the queue and raise if needed
        while not self._input_guardrail_queue.empty():
            guardrail_result = self._input_guardrail_queue.get_nowait()
            if guardrail_result.output.tripwire_triggered:
                tripwire_exc = InputGuardrailTripwireTriggered(guardrail_result)
                tripwire_exc.run_data = self._create_error_details()
                self._stored_exception = tripwire_exc

        # Check the tasks for any exceptions
        if self.run_loop_task and self.run_loop_task.done():
            if not self.run_loop_task.cancelled():
                run_impl_exc = self.run_loop_task.exception()
                if run_impl_exc and isinstance(run_impl_exc, Exception):
                    if isinstance(run_impl_exc, AgentsException) and run_impl_exc.run_data is None:
                        run_impl_exc.run_data = self._create_error_details()
                    self._stored_exception = run_impl_exc

        if self._input_guardrails_task and self._input_guardrails_task.done():
            if not self._input_guardrails_task.cancelled():
                in_guard_exc = self._input_guardrails_task.exception()
                if in_guard_exc and isinstance(in_guard_exc, Exception):
                    if isinstance(in_guard_exc, AgentsException) and in_guard_exc.run_data is None:
                        in_guard_exc.run_data = self._create_error_details()
                    self._stored_exception = in_guard_exc

        if self._output_guardrails_task and self._output_guardrails_task.done():
            if not self._output_guardrails_task.cancelled():
                out_guard_exc = self._output_guardrails_task.exception()
                if out_guard_exc and isinstance(out_guard_exc, Exception):
                    if (
                        isinstance(out_guard_exc, AgentsException)
                        and out_guard_exc.run_data is None
                    ):
                        out_guard_exc.run_data = self._create_error_details()
                    self._stored_exception = out_guard_exc

    def _cleanup_tasks(self):
        if self.run_loop_task and not self.run_loop_task.done():
            self.run_loop_task.cancel()

        if self._input_guardrails_task and not self._input_guardrails_task.done():
            self._input_guardrails_task.cancel()

        if self._output_guardrails_task and not self._output_guardrails_task.done():
            self._output_guardrails_task.cancel()

    def __str__(self) -> str:
        return pretty_print_run_result_streaming(self)

    async def _await_task_safely(self, task: asyncio.Task[Any] | None) -> None:
        """Await a task if present, ignoring cancellation and storing exceptions elsewhere.

        This ensures we do not lose late guardrail exceptions while not surfacing
        CancelledError to callers of stream_events.
        """
        if task and not task.done():
            try:
                await task
            except asyncio.CancelledError:
                # Task was cancelled (e.g., due to result.cancel()). Nothing to do here.
                pass
            except Exception:
                # The exception will be surfaced via _check_errors() if needed.
                pass

    def _drain_event_queue(self) -> None:
        """Remove any pending items from the event queue and mark them done."""
        while not self._event_queue.empty():
            try:
                self._event_queue.get_nowait()
                self._event_queue.task_done()
            except asyncio.QueueEmpty:
                break
            except ValueError:
                # task_done called too many times; nothing more to drain.
                break

    def _drain_input_guardrail_queue(self) -> None:
        """Remove any pending items from the input guardrail queue."""
        while not self._input_guardrail_queue.empty():
            try:
                self._input_guardrail_queue.get_nowait()
            except asyncio.QueueEmpty:
                break

    def to_state(self) -> RunState[Any]:
        """Create a RunState from this streaming result to resume execution.

        This is useful when the run was interrupted (e.g., for tool approval). You can
        approve or reject the tool calls on the returned state, then pass it back to
        `Runner.run_streamed()` to continue execution.

        Returns:
            A RunState that can be used to resume the run.

        Example:
            ```python
            # Run agent until it needs approval
            result = Runner.run_streamed(agent, "Use the delete_file tool")
            async for event in result.stream_events():
                pass

            if result.interruptions:
                # Approve the tool call
                state = result.to_state()
                state.approve(result.interruptions[0])

                # Resume the run
                result = Runner.run_streamed(agent, state)
                async for event in result.stream_events():
                    pass
            ```
        """
        # Create a RunState from the current result
        # Use _original_input (updated on handoffs/resume when input history changes).
        # This avoids serializing a mutated view of input history.
        state = RunState(
            context=self.context_wrapper,
            original_input=self._original_input if self._original_input is not None else self.input,
            starting_agent=_starting_agent_for_state(self),
            max_turns=self.max_turns,
        )

        return _populate_state_from_result(
            state,
            self,
            current_turn=self.current_turn,
            last_processed_response=self._last_processed_response,
            current_turn_persisted_item_count=self._current_turn_persisted_item_count,
            tool_use_tracker_snapshot=self._tool_use_tracker_snapshot,
            conversation_id=self._conversation_id,
            previous_response_id=self._previous_response_id,
            auto_previous_response_id=self._auto_previous_response_id,
        )

current_agent instance-attribute

current_agent: Agent[Any]

The current agent that is running.

current_turn instance-attribute

current_turn: int

The current turn number.

max_turns instance-attribute

max_turns: int | None

The maximum number of turns the agent can run for, or None for no limit.

final_output instance-attribute

final_output: Any

The final output of the agent. This is None until the agent has finished running.

is_complete class-attribute instance-attribute

is_complete: bool = False

Whether the agent has finished running.

interruptions class-attribute instance-attribute

interruptions: list[ToolApprovalItem] = field(
    default_factory=list
)

Pending tool approval requests (interruptions) for this run.

last_agent property

last_agent: Agent[Any]

The last agent that was run. Updates as the agent run progresses, so the true last agent is only available after the agent run is complete.

run_loop_exception property

run_loop_exception: BaseException | None

The exception raised by the background run loop, if any.

When the run loop fails before producing stream events (for example during early sandbox initialisation), the exception may not be re-raised through :meth:stream_events. This property gives callers a reliable way to check for silent failures after consuming the stream:

.. code-block:: python

result = Runner.run_streamed(agent, "hello")
async for event in result.stream_events():
    pass
if result.run_loop_exception:
    raise result.run_loop_exception

Returns None if the run loop completed without error, has not yet finished, or was cancelled.

input instance-attribute

input: str | list[TResponseInputItem]

The original input items i.e. the items before run() was called. This may be a mutated version of the input, if there are handoff input filters that mutate the input.

new_items instance-attribute

new_items: list[RunItem]

The new items generated during the agent run. These include things like new messages, tool calls and their outputs, etc.

raw_responses instance-attribute

raw_responses: list[ModelResponse]

The raw LLM responses generated by the model during the agent run.

input_guardrail_results instance-attribute

input_guardrail_results: list[InputGuardrailResult]

Guardrail results for the input messages.

output_guardrail_results instance-attribute

output_guardrail_results: list[OutputGuardrailResult]

Guardrail results for the final output of the agent.

tool_input_guardrail_results instance-attribute

tool_input_guardrail_results: list[ToolInputGuardrailResult]

Tool input guardrail results from all tools executed during the run.

tool_output_guardrail_results instance-attribute

tool_output_guardrail_results: list[
    ToolOutputGuardrailResult
]

Tool output guardrail results from all tools executed during the run.

context_wrapper instance-attribute

context_wrapper: RunContextWrapper[Any]

The context wrapper for the agent run.

agent_tool_invocation property

agent_tool_invocation: AgentToolInvocation | None

Immutable metadata for results produced by Agent.as_tool().

Returns None for ordinary top-level runs.

last_response_id property

last_response_id: str | None

Convenience method to get the response ID of the last model response.

cancel

cancel(
    mode: Literal["immediate", "after_turn"] = "immediate",
) -> None

Cancel the streaming run.

Parameters:

Name Type Description Default
mode Literal['immediate', 'after_turn']

Cancellation strategy: - "immediate": Stop immediately, cancel all tasks, clear queues (default) - "after_turn": Complete current turn gracefully before stopping * Allows LLM response to finish * Executes pending tool calls * Saves session state properly * Tracks usage accurately * Stops before next turn begins

'immediate'
Example
result = Runner.run_streamed(agent, "Task", session=session)

async for event in result.stream_events():
    if user_interrupted():
        result.cancel(mode="after_turn")  # Graceful
        # result.cancel()  # Immediate (default)

Note: After calling cancel(), you should continue consuming stream_events() to allow the cancellation to complete properly.

Source code in src/agents/result.py
def cancel(self, mode: Literal["immediate", "after_turn"] = "immediate") -> None:
    """Cancel the streaming run.

    Args:
        mode: Cancellation strategy:
            - "immediate": Stop immediately, cancel all tasks, clear queues (default)
            - "after_turn": Complete current turn gracefully before stopping
                * Allows LLM response to finish
                * Executes pending tool calls
                * Saves session state properly
                * Tracks usage accurately
                * Stops before next turn begins

    Example:
        ```python
        result = Runner.run_streamed(agent, "Task", session=session)

        async for event in result.stream_events():
            if user_interrupted():
                result.cancel(mode="after_turn")  # Graceful
                # result.cancel()  # Immediate (default)
        ```

    Note: After calling cancel(), you should continue consuming stream_events()
    to allow the cancellation to complete properly.
    """
    # Store the cancel mode for the background task to check
    self._cancel_mode = mode

    if mode == "immediate":
        # Existing behavior - immediate shutdown
        self._cleanup_tasks()  # Cancel all running tasks
        self.is_complete = True  # Mark the run as complete to stop event streaming

        while not self._input_guardrail_queue.empty():
            self._input_guardrail_queue.get_nowait()

        # Unblock any streamers waiting on the event queue.
        self._event_queue.put_nowait(QueueCompleteSentinel())
        if not self._waiting_on_event_queue:
            self._drain_event_queue()

    elif mode == "after_turn":
        # Soft cancel - just set the flag
        # The streaming loop will check this and stop gracefully
        # Don't call _cleanup_tasks() or clear queues yet
        pass

stream_events async

stream_events() -> AsyncIterator[StreamEvent]

Stream deltas for new items as they are generated. We're using the types from the OpenAI Responses API, so these are semantic events: each event has a type field that describes the type of the event, along with the data for that event.

This will raise: - A MaxTurnsExceeded exception if the agent exceeds the max_turns limit. - A GuardrailTripwireTriggered exception if a guardrail is tripped.

Source code in src/agents/result.py
async def stream_events(self) -> AsyncIterator[StreamEvent]:
    """Stream deltas for new items as they are generated. We're using the types from the
    OpenAI Responses API, so these are semantic events: each event has a `type` field that
    describes the type of the event, along with the data for that event.

    This will raise:
    - A MaxTurnsExceeded exception if the agent exceeds the max_turns limit.
    - A GuardrailTripwireTriggered exception if a guardrail is tripped.
    """
    cancelled = False
    try:
        while True:
            self._check_errors()
            should_drain_queued_events = isinstance(
                self._stored_exception, MaxTurnsExceeded
            ) or (
                self._stored_exception is not None
                and _should_drain_stream_events_before_raising(self._stored_exception)
            )
            if self._stored_exception and (
                not should_drain_queued_events or self._event_queue.empty()
            ):
                logger.debug("Breaking due to stored exception")
                self.is_complete = True
                break

            if self.is_complete and self._event_queue.empty():
                break

            try:
                self._waiting_on_event_queue = True
                item = await self._event_queue.get()
            except asyncio.CancelledError:
                cancelled = True
                self.cancel()
                raise
            finally:
                self._waiting_on_event_queue = False

            if isinstance(item, QueueCompleteSentinel):
                # Await input guardrails if they are still running, so late
                # exceptions are captured.
                await self._await_task_safely(self._input_guardrails_task)

                self._event_queue.task_done()

                # Check for errors, in case the queue was completed
                # due to an exception
                self._check_errors()
                break

            yield item
            self._event_queue.task_done()
    finally:
        try:
            if cancelled:
                # Cancellation should return promptly, so avoid waiting on long-running tasks.
                # Tasks have already been cancelled above.
                self._cleanup_tasks()
            else:
                # Ensure main execution completes before cleanup to avoid race conditions
                # with session operations.
                await self._await_task_safely(self.run_loop_task)
                # Re-check for exceptions now that the run loop has fully settled.
                # _await_task_safely swallows exceptions; without this call, a run-loop
                # failure that races past the sentinel (e.g. early sandbox failures) would
                # be silently lost instead of surfaced via _stored_exception.
                self._check_errors()
                # Safely terminate all background tasks after main execution has finished.
                self._cleanup_tasks()

            if not cancelled:
                await self._run_sandbox_cleanup()
        finally:
            # Allow any pending callbacks (e.g., cancellation handlers) to enqueue their
            # completion sentinels before we clear the queues for observability.
            await asyncio.sleep(0)

            # Drain queues so callers observing internal state see them empty after completion.
            self._drain_event_queue()
            self._drain_input_guardrail_queue()

    if self._stored_exception:
        raise self._stored_exception

to_state

to_state() -> RunState[Any]

Create a RunState from this streaming result to resume execution.

This is useful when the run was interrupted (e.g., for tool approval). You can approve or reject the tool calls on the returned state, then pass it back to Runner.run_streamed() to continue execution.

Returns:

Type Description
RunState[Any]

A RunState that can be used to resume the run.

Example
# Run agent until it needs approval
result = Runner.run_streamed(agent, "Use the delete_file tool")
async for event in result.stream_events():
    pass

if result.interruptions:
    # Approve the tool call
    state = result.to_state()
    state.approve(result.interruptions[0])

    # Resume the run
    result = Runner.run_streamed(agent, state)
    async for event in result.stream_events():
        pass
Source code in src/agents/result.py
def to_state(self) -> RunState[Any]:
    """Create a RunState from this streaming result to resume execution.

    This is useful when the run was interrupted (e.g., for tool approval). You can
    approve or reject the tool calls on the returned state, then pass it back to
    `Runner.run_streamed()` to continue execution.

    Returns:
        A RunState that can be used to resume the run.

    Example:
        ```python
        # Run agent until it needs approval
        result = Runner.run_streamed(agent, "Use the delete_file tool")
        async for event in result.stream_events():
            pass

        if result.interruptions:
            # Approve the tool call
            state = result.to_state()
            state.approve(result.interruptions[0])

            # Resume the run
            result = Runner.run_streamed(agent, state)
            async for event in result.stream_events():
                pass
        ```
    """
    # Create a RunState from the current result
    # Use _original_input (updated on handoffs/resume when input history changes).
    # This avoids serializing a mutated view of input history.
    state = RunState(
        context=self.context_wrapper,
        original_input=self._original_input if self._original_input is not None else self.input,
        starting_agent=_starting_agent_for_state(self),
        max_turns=self.max_turns,
    )

    return _populate_state_from_result(
        state,
        self,
        current_turn=self.current_turn,
        last_processed_response=self._last_processed_response,
        current_turn_persisted_item_count=self._current_turn_persisted_item_count,
        tool_use_tracker_snapshot=self._tool_use_tracker_snapshot,
        conversation_id=self._conversation_id,
        previous_response_id=self._previous_response_id,
        auto_previous_response_id=self._auto_previous_response_id,
    )

release_agents

release_agents(*, release_new_items: bool = True) -> None

Release strong references to agents held by this result. After calling this method, accessing item.agent or last_agent may return None if the agent has been garbage collected. Callers can use this when they are done inspecting the result and want to eagerly drop any associated agent graph.

Source code in src/agents/result.py
def release_agents(self, *, release_new_items: bool = True) -> None:
    """
    Release strong references to agents held by this result. After calling this method,
    accessing `item.agent` or `last_agent` may return `None` if the agent has been garbage
    collected. Callers can use this when they are done inspecting the result and want to
    eagerly drop any associated agent graph.
    """
    if release_new_items:
        for item in self.new_items:
            release = getattr(item, "release_agent", None)
            if callable(release):
                release()
    self._release_last_agent_reference()

final_output_as

final_output_as(
    cls: type[T], raise_if_incorrect_type: bool = False
) -> T

A convenience method to cast the final output to a specific type. By default, the cast is only for the typechecker. If you set raise_if_incorrect_type to True, we'll raise a TypeError if the final output is not of the given type.

Parameters:

Name Type Description Default
cls type[T]

The type to cast the final output to.

required
raise_if_incorrect_type bool

If True, we'll raise a TypeError if the final output is not of the given type.

False

Returns:

Type Description
T

The final output casted to the given type.

Source code in src/agents/result.py
def final_output_as(self, cls: type[T], raise_if_incorrect_type: bool = False) -> T:
    """A convenience method to cast the final output to a specific type. By default, the cast
    is only for the typechecker. If you set `raise_if_incorrect_type` to True, we'll raise a
    TypeError if the final output is not of the given type.

    Args:
        cls: The type to cast the final output to.
        raise_if_incorrect_type: If True, we'll raise a TypeError if the final output is not of
            the given type.

    Returns:
        The final output casted to the given type.
    """
    if raise_if_incorrect_type and not isinstance(self.final_output, cls):
        raise TypeError(f"Final output is not of type {cls.__name__}")

    return cast(T, self.final_output)

to_input_list

to_input_list(
    *, mode: ToInputListMode = "preserve_all"
) -> list[TResponseInputItem]

Create an input-item view of this run.

mode="preserve_all" keeps the historical behavior of converting new_items into a full plain-item history. mode="normalized" prefers the canonical continuation input when handoff filtering rewrote model history, while remaining identical for ordinary runs.

Source code in src/agents/result.py
def to_input_list(
    self,
    *,
    mode: ToInputListMode = "preserve_all",
) -> list[TResponseInputItem]:
    """Create an input-item view of this run.

    ``mode="preserve_all"`` keeps the historical behavior of converting ``new_items`` into a
    full plain-item history. ``mode="normalized"`` prefers the canonical continuation input
    when handoff filtering rewrote model history, while remaining identical for ordinary runs.
    """
    public_input = self.input
    owned_item_refs = _reconciled_result_owned_item_refs(self, public_input)
    original_items = ItemHelpers.input_to_new_input_list(public_input)
    reasoning_item_id_policy = getattr(self, "_reasoning_item_id_policy", None)
    replay_items = _input_items_for_result(
        self,
        mode=mode,
        reasoning_item_id_policy=reasoning_item_id_policy,
        public_input=public_input,
        owned_item_refs=owned_item_refs,
    )
    return original_items + replay_items