From b3a8aab85f526d4b47835b46746bde93758011b3 Mon Sep 17 00:00:00 2001 From: Alexander Alderman Webb Date: Wed, 11 Feb 2026 13:53:25 +0100 Subject: [PATCH 01/10] docs: Document openai-agents control-flow --- sentry_sdk/integrations/openai_agents/__init__.py | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/sentry_sdk/integrations/openai_agents/__init__.py b/sentry_sdk/integrations/openai_agents/__init__.py index deb136de01..7f119807d4 100644 --- a/sentry_sdk/integrations/openai_agents/__init__.py +++ b/sentry_sdk/integrations/openai_agents/__init__.py @@ -52,6 +52,18 @@ def _patch_tools() -> None: class OpenAIAgentsIntegration(Integration): + """ + Typical interaction with the library: + 1. The user creates an Agent instance with configuration, including system instructions sent to every Responses API call. + 2. The user passes the agent instance to a Runner, which returns the agent execution response synchronously or asynchronously (the latter is called streaming). + 3. In a loop, the agent repeatedly calls the Responses API, maintaining a conversation history that includes previous messages and tool results, which is passed to each call. + + Local tools are run based on the return value from the Responses API as a post-API call step in the above loop. + Hosted MCP Tools are run as part of the Responses API call, and involve OpenAI reaching out to an external MCP server. + An agent can handoff to another agent, also directed by the return value of the Responses API and run post-API call in the loop. + Handoffs are a way to switch agent-wide configuration. + """ + identifier = "openai_agents" @staticmethod From 802bbfdc2abf0b265d67b0b0667e5d9f04c838cd Mon Sep 17 00:00:00 2001 From: Alexander Alderman Webb Date: Wed, 11 Feb 2026 13:57:01 +0100 Subject: [PATCH 02/10] . --- sentry_sdk/integrations/openai_agents/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sentry_sdk/integrations/openai_agents/__init__.py b/sentry_sdk/integrations/openai_agents/__init__.py index 7f119807d4..388cbcfde6 100644 --- a/sentry_sdk/integrations/openai_agents/__init__.py +++ b/sentry_sdk/integrations/openai_agents/__init__.py @@ -55,7 +55,7 @@ class OpenAIAgentsIntegration(Integration): """ Typical interaction with the library: 1. The user creates an Agent instance with configuration, including system instructions sent to every Responses API call. - 2. The user passes the agent instance to a Runner, which returns the agent execution response synchronously or asynchronously (the latter is called streaming). + 2. The user passes the agent instance to a Runner with `run()` and `run_streamed()` methods. The latter can be used to incrementally receive progress. 3. In a loop, the agent repeatedly calls the Responses API, maintaining a conversation history that includes previous messages and tool results, which is passed to each call. Local tools are run based on the return value from the Responses API as a post-API call step in the above loop. From e28ddd3f40e04330e5611a0352bb573995c79aef Mon Sep 17 00:00:00 2001 From: Alexander Alderman Webb Date: Wed, 11 Feb 2026 14:46:40 +0100 Subject: [PATCH 03/10] reference patched functions --- sentry_sdk/integrations/openai_agents/__init__.py | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/sentry_sdk/integrations/openai_agents/__init__.py b/sentry_sdk/integrations/openai_agents/__init__.py index 388cbcfde6..ea869d8a7a 100644 --- a/sentry_sdk/integrations/openai_agents/__init__.py +++ b/sentry_sdk/integrations/openai_agents/__init__.py @@ -53,15 +53,29 @@ def _patch_tools() -> None: class OpenAIAgentsIntegration(Integration): """ + NOTE: With version 0.8.0, the class methods below have been refactored to functions. + - `AgentRunner._get_model()` -> `agents.run_internal.turn_preparation.get_model()` + - `AgentRunner._get_all_tools()` -> `agents.run_internal.turn_preparation.get_all_tools()` + - `AgentRunner._run_single_turn()` -> `agents.run_internal.run_loop.run_single_turn()` + - `RunImpl.execute_handoffs()` -> `agents.run_internal.turn_resolution.execute_handoffs()` + - `RunImpl.execute_final_output()` -> `agents.run_internal.turn_resolution.execute_final_output()` + Typical interaction with the library: 1. The user creates an Agent instance with configuration, including system instructions sent to every Responses API call. 2. The user passes the agent instance to a Runner with `run()` and `run_streamed()` methods. The latter can be used to incrementally receive progress. + - `Runner.run()` and `Runner.run_streamed()` are thin wrappers for `DEFAULT_AGENT_RUNNER.run()` and `DEFAULT_AGENT_RUNNER.run_streamed()`. + - `DEFAULT_AGENT_RUNNER.run()` and `DEFAULT_AGENT_RUNNER.run_streamed()` are patched in `_patch_runner()` with `_create_run_wrapper()` and `_create_run_streamed_wrapper()`, respectively. 3. In a loop, the agent repeatedly calls the Responses API, maintaining a conversation history that includes previous messages and tool results, which is passed to each call. + - A Model instance is created at the start of the loop by calling the `Runner._get_model()`. We patch the Model instance using `_create_get_model_wrapper()` in `_patch_model()`. + - Available tools are also deteremined at the start of the loop, with `Runner._get_all_tools()`. We patch the method using `_create_get_all_tools_wrapper()` in `_patch_tools()` + - In each loop execution, `run_single_turn()` or `run_single_turn_streamed()` is responsible for calling the Responses API, patched with `patched_run_single_turn()` and `patched_run_single_turn_streamed()`. + 4. On loop termination, `RunImpl.execute_final_output()` is called. The function is patched with `patched_execute_final_output()`. Local tools are run based on the return value from the Responses API as a post-API call step in the above loop. Hosted MCP Tools are run as part of the Responses API call, and involve OpenAI reaching out to an external MCP server. An agent can handoff to another agent, also directed by the return value of the Responses API and run post-API call in the loop. Handoffs are a way to switch agent-wide configuration. + - Handoffs are executed by calling `RunImpl.execute_handoffs()`. The method is patched in `patched_execute_handoffs()` """ identifier = "openai_agents" From fc35b222a620f5c462956cf6ea0647b49a7c0b61 Mon Sep 17 00:00:00 2001 From: Alexander Alderman Webb Date: Wed, 11 Feb 2026 17:10:12 +0100 Subject: [PATCH 04/10] . --- sentry_sdk/integrations/openai_agents/__init__.py | 8 +++++++- sentry_sdk/integrations/openai_agents/patches/runner.py | 2 ++ 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/sentry_sdk/integrations/openai_agents/__init__.py b/sentry_sdk/integrations/openai_agents/__init__.py index ea869d8a7a..7e3e9e5301 100644 --- a/sentry_sdk/integrations/openai_agents/__init__.py +++ b/sentry_sdk/integrations/openai_agents/__init__.py @@ -40,12 +40,18 @@ def _patch_runner() -> None: def _patch_model() -> None: + """ + Responsible for `gen_ai.chat` spans. + """ agents.run.AgentRunner._get_model = classmethod( _create_get_model_wrapper(agents.run.AgentRunner._get_model), ) def _patch_tools() -> None: + """ + Responsible for `gen_ai.execute_tool` spans. + """ agents.run.AgentRunner._get_all_tools = classmethod( _create_get_all_tools_wrapper(agents.run.AgentRunner._get_all_tools), ) @@ -67,7 +73,7 @@ class OpenAIAgentsIntegration(Integration): - `DEFAULT_AGENT_RUNNER.run()` and `DEFAULT_AGENT_RUNNER.run_streamed()` are patched in `_patch_runner()` with `_create_run_wrapper()` and `_create_run_streamed_wrapper()`, respectively. 3. In a loop, the agent repeatedly calls the Responses API, maintaining a conversation history that includes previous messages and tool results, which is passed to each call. - A Model instance is created at the start of the loop by calling the `Runner._get_model()`. We patch the Model instance using `_create_get_model_wrapper()` in `_patch_model()`. - - Available tools are also deteremined at the start of the loop, with `Runner._get_all_tools()`. We patch the method using `_create_get_all_tools_wrapper()` in `_patch_tools()` + - Available tools are also deteremined at the start of the loop, with `Runner._get_all_tools()`. We patch Tool instances by iterating through the returned tools, in `_create_get_all_tools_wrapper()` called via `_patch_tools()` - In each loop execution, `run_single_turn()` or `run_single_turn_streamed()` is responsible for calling the Responses API, patched with `patched_run_single_turn()` and `patched_run_single_turn_streamed()`. 4. On loop termination, `RunImpl.execute_final_output()` is called. The function is patched with `patched_execute_final_output()`. diff --git a/sentry_sdk/integrations/openai_agents/patches/runner.py b/sentry_sdk/integrations/openai_agents/patches/runner.py index b561500495..44d8c1aca9 100644 --- a/sentry_sdk/integrations/openai_agents/patches/runner.py +++ b/sentry_sdk/integrations/openai_agents/patches/runner.py @@ -22,6 +22,8 @@ def _create_run_wrapper(original_func: "Callable[..., Any]") -> "Callable[..., Any]": """ + Responsible for workflow spans. + Wraps the agents.Runner.run methods to create a root span for the agent workflow runs. Note agents.Runner.run_sync() is a wrapper around agents.Runner.run(), From 4e865a1d9f1ffa7959a7d989ed744e2a1d0c39bf Mon Sep 17 00:00:00 2001 From: Alexander Alderman Webb Date: Wed, 11 Feb 2026 17:11:03 +0100 Subject: [PATCH 05/10] . --- sentry_sdk/integrations/openai_agents/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sentry_sdk/integrations/openai_agents/__init__.py b/sentry_sdk/integrations/openai_agents/__init__.py index 7e3e9e5301..38a6b06261 100644 --- a/sentry_sdk/integrations/openai_agents/__init__.py +++ b/sentry_sdk/integrations/openai_agents/__init__.py @@ -74,7 +74,7 @@ class OpenAIAgentsIntegration(Integration): 3. In a loop, the agent repeatedly calls the Responses API, maintaining a conversation history that includes previous messages and tool results, which is passed to each call. - A Model instance is created at the start of the loop by calling the `Runner._get_model()`. We patch the Model instance using `_create_get_model_wrapper()` in `_patch_model()`. - Available tools are also deteremined at the start of the loop, with `Runner._get_all_tools()`. We patch Tool instances by iterating through the returned tools, in `_create_get_all_tools_wrapper()` called via `_patch_tools()` - - In each loop execution, `run_single_turn()` or `run_single_turn_streamed()` is responsible for calling the Responses API, patched with `patched_run_single_turn()` and `patched_run_single_turn_streamed()`. + - In each loop iteration, `run_single_turn()` or `run_single_turn_streamed()` is responsible for calling the Responses API, patched with `patched_run_single_turn()` and `patched_run_single_turn_streamed()`. 4. On loop termination, `RunImpl.execute_final_output()` is called. The function is patched with `patched_execute_final_output()`. Local tools are run based on the return value from the Responses API as a post-API call step in the above loop. From b05f1600fc5399fe41b333d69bb3f751be969e82 Mon Sep 17 00:00:00 2001 From: Alexander Alderman Webb Date: Wed, 11 Feb 2026 17:36:28 +0100 Subject: [PATCH 06/10] explaining responsibility of each patch --- .../integrations/openai_agents/__init__.py | 6 ----- .../openai_agents/patches/agent_run.py | 24 +++++++++++++++---- .../openai_agents/patches/models.py | 5 ++++ .../openai_agents/patches/runner.py | 6 ++--- .../openai_agents/patches/tools.py | 2 ++ 5 files changed, 30 insertions(+), 13 deletions(-) diff --git a/sentry_sdk/integrations/openai_agents/__init__.py b/sentry_sdk/integrations/openai_agents/__init__.py index 38a6b06261..50cdae222b 100644 --- a/sentry_sdk/integrations/openai_agents/__init__.py +++ b/sentry_sdk/integrations/openai_agents/__init__.py @@ -40,18 +40,12 @@ def _patch_runner() -> None: def _patch_model() -> None: - """ - Responsible for `gen_ai.chat` spans. - """ agents.run.AgentRunner._get_model = classmethod( _create_get_model_wrapper(agents.run.AgentRunner._get_model), ) def _patch_tools() -> None: - """ - Responsible for `gen_ai.execute_tool` spans. - """ agents.run.AgentRunner._get_all_tools = classmethod( _create_get_all_tools_wrapper(agents.run.AgentRunner._get_all_tools), ) diff --git a/sentry_sdk/integrations/openai_agents/patches/agent_run.py b/sentry_sdk/integrations/openai_agents/patches/agent_run.py index eeb821d42a..02a649f03e 100644 --- a/sentry_sdk/integrations/openai_agents/patches/agent_run.py +++ b/sentry_sdk/integrations/openai_agents/patches/agent_run.py @@ -94,7 +94,11 @@ def _maybe_start_agent_span( async def patched_run_single_turn( cls: "agents.Runner", *args: "Any", **kwargs: "Any" ) -> "Any": - """Patched _run_single_turn that creates agent invocation spans""" + """ + Patched _run_single_turn that + - creates agent invocation spans if there is no already active agent invocation span. + - ends the agent invocation span if and only if an exception is raised in `_run_single_turn()`. + """ agent = kwargs.get("agent") context_wrapper = kwargs.get("context_wrapper") should_run_agent_start_hooks = kwargs.get("should_run_agent_start_hooks", False) @@ -121,7 +125,12 @@ async def patched_run_single_turn( async def patched_execute_handoffs( cls: "agents.Runner", *args: "Any", **kwargs: "Any" ) -> "Any": - """Patched execute_handoffs that creates handoff spans and ends agent span for handoffs""" + """ + Patched execute_handoffs that + - creates handoff spans. + - ends the agent span for handoffs. + - ends the workflow span if the response is streamed and an exception is raised in `execute_handoffs()`. + """ context_wrapper = kwargs.get("context_wrapper") run_handoffs = kwargs.get("run_handoffs") @@ -156,7 +165,11 @@ async def patched_execute_handoffs( async def patched_execute_final_output( cls: "agents.Runner", *args: "Any", **kwargs: "Any" ) -> "Any": - """Patched execute_final_output that ends agent span for final outputs""" + """ + Patched execute_final_output that + - ends agent span for final outputs. + - ends the workflow span if the response is streamed. + """ agent = kwargs.get("agent") context_wrapper = kwargs.get("context_wrapper") @@ -185,7 +198,10 @@ async def patched_execute_final_output( async def patched_run_single_turn_streamed( cls: "agents.Runner", *args: "Any", **kwargs: "Any" ) -> "Any": - """Patched _run_single_turn_streamed that creates agent invocation spans for streaming. + """ + Patched _run_single_turn_streamed that + - creates agent invocation spans for streaming if there is no already active agent invocation span. + - ends the agent invocation span if and only if `_run_single_turn_streamed()` raises an exception. Note: Unlike _run_single_turn which uses keyword-only arguments (*,), _run_single_turn_streamed uses positional arguments. The call signature is: diff --git a/sentry_sdk/integrations/openai_agents/patches/models.py b/sentry_sdk/integrations/openai_agents/patches/models.py index b6a69ae9f7..5d4d71185f 100644 --- a/sentry_sdk/integrations/openai_agents/patches/models.py +++ b/sentry_sdk/integrations/openai_agents/patches/models.py @@ -71,6 +71,11 @@ def _create_get_model_wrapper( ) -> "Callable[..., Any]": """ Wraps the agents.Runner._get_model method to wrap the get_response method of the model to create a AI client span. + + Responsible for + - creating and managing AI client spans. + - adding trace propagation headers to tools with type HostedMCPTool. + - setting the response model on agent invocation spans. """ @wraps( diff --git a/sentry_sdk/integrations/openai_agents/patches/runner.py b/sentry_sdk/integrations/openai_agents/patches/runner.py index 44d8c1aca9..217cfd434d 100644 --- a/sentry_sdk/integrations/openai_agents/patches/runner.py +++ b/sentry_sdk/integrations/openai_agents/patches/runner.py @@ -22,9 +22,9 @@ def _create_run_wrapper(original_func: "Callable[..., Any]") -> "Callable[..., Any]": """ - Responsible for workflow spans. - - Wraps the agents.Runner.run methods to create a root span for the agent workflow runs. + Wraps the agents.Runner.run methods to + - create a root span for the agent workflow runs. + - ending the agent invocation span if an `AgentsException` is raised in `run()`. Note agents.Runner.run_sync() is a wrapper around agents.Runner.run(), so it does not need to be wrapped separately. diff --git a/sentry_sdk/integrations/openai_agents/patches/tools.py b/sentry_sdk/integrations/openai_agents/patches/tools.py index d14a3019aa..56047e20c5 100644 --- a/sentry_sdk/integrations/openai_agents/patches/tools.py +++ b/sentry_sdk/integrations/openai_agents/patches/tools.py @@ -19,6 +19,8 @@ def _create_get_all_tools_wrapper( original_get_all_tools: "Callable[..., Any]", ) -> "Callable[..., Any]": """ + Responsible for creating and managing `gen_ai.execute_tool` spans. + Wraps the agents.Runner._get_all_tools method of the Runner class to wrap all function tools with Sentry instrumentation. """ From 5d24a223aef49f9c419bd085c9ffe9d1adf9b41b Mon Sep 17 00:00:00 2001 From: Alexander Alderman Webb Date: Wed, 11 Feb 2026 17:37:38 +0100 Subject: [PATCH 07/10] . --- sentry_sdk/integrations/openai_agents/patches/agent_run.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sentry_sdk/integrations/openai_agents/patches/agent_run.py b/sentry_sdk/integrations/openai_agents/patches/agent_run.py index 02a649f03e..ff71a25f7b 100644 --- a/sentry_sdk/integrations/openai_agents/patches/agent_run.py +++ b/sentry_sdk/integrations/openai_agents/patches/agent_run.py @@ -167,7 +167,7 @@ async def patched_execute_final_output( ) -> "Any": """ Patched execute_final_output that - - ends agent span for final outputs. + - ends the agent invocation span. - ends the workflow span if the response is streamed. """ From 35c99f5da65bff7963138fa0365aa24392d6b8c0 Mon Sep 17 00:00:00 2001 From: Alexander Alderman Webb Date: Wed, 11 Feb 2026 17:43:35 +0100 Subject: [PATCH 08/10] . --- sentry_sdk/integrations/openai_agents/patches/agent_run.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/sentry_sdk/integrations/openai_agents/patches/agent_run.py b/sentry_sdk/integrations/openai_agents/patches/agent_run.py index ff71a25f7b..138151d930 100644 --- a/sentry_sdk/integrations/openai_agents/patches/agent_run.py +++ b/sentry_sdk/integrations/openai_agents/patches/agent_run.py @@ -127,8 +127,8 @@ async def patched_execute_handoffs( ) -> "Any": """ Patched execute_handoffs that - - creates handoff spans. - - ends the agent span for handoffs. + - creates and manages handoff spans. + - ends the agent invocation span. - ends the workflow span if the response is streamed and an exception is raised in `execute_handoffs()`. """ From cfbf3a99e82422bec6eab3adbd864b17890a3dae Mon Sep 17 00:00:00 2001 From: Alexander Alderman Webb Date: Wed, 11 Feb 2026 17:45:21 +0100 Subject: [PATCH 09/10] . --- sentry_sdk/integrations/openai_agents/patches/runner.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/sentry_sdk/integrations/openai_agents/patches/runner.py b/sentry_sdk/integrations/openai_agents/patches/runner.py index 217cfd434d..91c2cfd8c2 100644 --- a/sentry_sdk/integrations/openai_agents/patches/runner.py +++ b/sentry_sdk/integrations/openai_agents/patches/runner.py @@ -23,8 +23,8 @@ def _create_run_wrapper(original_func: "Callable[..., Any]") -> "Callable[..., Any]": """ Wraps the agents.Runner.run methods to - - create a root span for the agent workflow runs. - - ending the agent invocation span if an `AgentsException` is raised in `run()`. + - create and manage a root span for the agent workflow runs. + - end the agent invocation span if an `AgentsException` is raised in `run()`. Note agents.Runner.run_sync() is a wrapper around agents.Runner.run(), so it does not need to be wrapped separately. From 5938849793431f12975eed638fdf25fa660cadc4 Mon Sep 17 00:00:00 2001 From: Alexander Alderman Webb Date: Wed, 11 Feb 2026 17:48:04 +0100 Subject: [PATCH 10/10] . --- sentry_sdk/integrations/openai_agents/patches/runner.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/sentry_sdk/integrations/openai_agents/patches/runner.py b/sentry_sdk/integrations/openai_agents/patches/runner.py index 91c2cfd8c2..78c055890e 100644 --- a/sentry_sdk/integrations/openai_agents/patches/runner.py +++ b/sentry_sdk/integrations/openai_agents/patches/runner.py @@ -87,7 +87,9 @@ def _create_run_streamed_wrapper( original_func: "Callable[..., Any]", ) -> "Callable[..., Any]": """ - Wraps the agents.Runner.run_streamed method to create a root span for streaming agent workflow runs. + Wraps the agents.Runner.run_streamed method to + - create a root span for streaming agent workflow runs. + - end the workflow span if and only if the response stream is consumed or cancelled. Unlike run(), run_streamed() returns immediately with a RunResultStreaming object while execution continues in a background task. The workflow span must stay open