.NET: fix: preserve agent continuation token when wrapped with UseOpenTelemetry - #8265
Open
Atharva Vichare (atty57) wants to merge 2 commits into
Open
.NET: fix: preserve agent continuation token when wrapped with UseOpenTelemetry#8265Atharva Vichare (atty57) wants to merge 2 commits into
Atharva Vichare (atty57) wants to merge 2 commits into
Conversation
OpenTelemetryAgent routes runs through OpenTelemetryChatClient, so every response makes an AgentResponse -> ChatResponse -> AgentResponse round trip. AsChatResponse/AsChatResponseUpdate return the inner agent's own ChatResponse when that is the response's raw representation, so the round trip dropped agent-level state: for a ChatClientAgent the caller received the provider's raw continuation token instead of the ChatClientAgentContinuationToken, and resuming with it threw. AgentId was lost the same way. Wrap the inner agent's response in a ChatResponse that keeps the response itself as the raw representation, so RunCoreAsync/RunCoreStreamingAsync hand back the original instance unchanged. Fixes microsoft#8243
Atharva Vichare (atty57)
temporarily deployed
to
github-app-auth
September 10, 2026 17:48 — with
GitHub Actions
Inactive
Atharva Vichare (atty57)
temporarily deployed
to
github-app-auth
September 10, 2026 17:48 — with
GitHub Actions
Inactive
Atharva Vichare (atty57)
temporarily deployed
to
github-app-auth
September 10, 2026 17:49 — with
GitHub Actions
Inactive
Atharva Vichare (atty57)
temporarily deployed
to
github-app-auth
September 10, 2026 17:49 — with
GitHub Actions
Inactive
Copilot started reviewing on behalf of
Atharva Vichare (atty57)
September 10, 2026 17:49
View session
Contributor
There was a problem hiding this comment.
🟡 Changes recommended
Both new wrappers omit ModelId, causing response-model telemetry loss for regular and streaming calls.
Once you've addressed the issues Copilot identified, you can request another Copilot review.
Pull request overview
Fixes #8243 by preserving agent-level continuation state through OpenTelemetry wrappers.
Changes:
- Preserves original agent responses and streaming updates.
- Adds continuation-token serialization/resumption tests.
- Retains conversation IDs for telemetry.
File summaries
| File | Description |
|---|---|
dotnet/src/Microsoft.Agents.AI/OpenTelemetryAgent.cs |
Wraps responses while retaining agent state. |
dotnet/tests/Microsoft.Agents.AI.UnitTests/OpenTelemetryAgentTests.cs |
Tests token preservation and resumption. |
Review details
Suppressed comments (1)
dotnet/src/Microsoft.Agents.AI/OpenTelemetryAgent.cs:360
- The streaming wrapper similarly omits
ChatResponseUpdate.ModelId.OpenTelemetryChatClientconsumes the model ID from updates for chunk metrics and from the assembled response forgen_ai.response.model, so streamed calls lose that telemetry after this change. Preserve the raw update's model ID here as well.
ConversationId = (update.RawRepresentation as ChatResponseUpdate)?.ConversationId,
- Files reviewed: 2/2 changed files
- Comments generated: 1
- Review effort level: Balanced
💡 Add a code-review agent skill for context-aware, tailored reviews. Learn more in the docs.
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
Atharva Vichare (atty57)
temporarily deployed
to
github-app-auth
September 10, 2026 18:02 — with
GitHub Actions
Inactive
Atharva Vichare (atty57)
marked this pull request as ready for review
September 10, 2026 18:02
Atharva Vichare (atty57)
temporarily deployed
to
github-app-auth
September 10, 2026 18:03 — with
GitHub Actions
Inactive
Contributor
There was a problem hiding this comment.
🔵 Needs a closer look
Streaming conversion omits ModelId, causing response-model telemetry loss.
Review details
Suppressed comments (1)
Previously missed (1) — in code that hasn't changed since the last review.
dotnet/src/Microsoft.Agents.AI/OpenTelemetryAgent.cs:361
ModelIdis omitted from the streaming wrapper. Providers such asFoundryChatClientsetChatResponseUpdate.ModelId, andOpenTelemetryChatClientreads it to emit the response-model span and metric tags. Because this new wrapper is what telemetry processes, streaming runs now lose that metadata while the non-streaming helper preserves it. Copy the raw update'sModelIdas well.
- Files reviewed: 2/2 changed files
- Comments generated: 0 new
- Review effort level: Balanced
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Motivation & Context
Wrapping an agent with
UseOpenTelemetrybreaks background responses. The continuation token returned from a backgroundRunAsyncis the underlying provider's raw token rather than the agent's ownChatClientAgentContinuationToken, so an opaqueToBytes/FromBytessave-and-restore followed by a resume throws:The same flow works when OpenTelemetry is not in the pipeline. This blocks the scenario where background responses are started by one process and polled or resumed by another, since that requires persisting a serialized session and token while still emitting telemetry for logging and traceability.
OpenTelemetryAgentdelegates to the inner agent throughOpenTelemetryChatClientand aForwardingChatClient, so every response makes anAgentResponse->ChatResponse->AgentResponseround trip.AgentResponseExtensions.AsChatResponsereturns the inner agent's ownChatResponsedirectly whenever that is the response's raw representation, which is exactly the case forChatClientAgent. The wrapped continuation token andAgentIdlive only on theAgentResponse, so both were discarded on the way back out andRunCoreAsyncrebuilt a response from the provider'sChatResponse, carrying the provider's raw token.RunStreamingAsynclost them the same way throughAsChatResponseUpdate.Description & Review Guide
What are the major changes?
ForwardingChatClientno longer usesAsChatResponse/AsChatResponseUpdate. It now builds theChatResponse/ChatResponseUpdatethat is handed toOpenTelemetryChatClientwith the inner agent's own response as theRawRepresentation.RunCoreAsyncandRunCoreStreamingAsyncalready looked for anAgentResponse/AgentResponseUpdatethere, so they now return the inner agent's instance unchanged instead of reconstructing a lossy copy.ConversationIdis copied across from the innerChatResponse/ChatResponseUpdateso the chat span keeps emittinggen_ai.conversation.id.OpenTelemetryAgentTests: continuation token preservation forRunAsyncandRunStreamingAsync, and the reported scenario end to end - serialize the token withToBytes, restore it withFromBytes, resume, and assert the chat client receives its own token back. All three fail without the source change.What is the impact of these changes?
AgentId, and any other agent-level response state now survive aUseOpenTelemetrywrapper. Callers no longer need workarounds to reconstruct the agent token.ForwardingChatClient;AsChatResponse/AsChatResponseUpdatekeep their documented short-circuit behavior and are untouched.What do you want reviewers to focus on?
ConversationIdfrom the raw representation is the right way to keep the chat span intact, or whether the span should take it from the request options instead.DelegatingAIAgentimplementations that perform the same round trip should share a helper rather than keeping this private toOpenTelemetryAgent.Related Issue
Fixes #8243
Contribution Checklist
breaking changelabel (or add "[BREAKING]" to the title prefix, before or after any language prefix) — a workflow keeps the label and title prefix in sync automatically.