Skip to content

.NET: DRAFT feat: Implement message filtering to exclude non-portable content typ…#5410

Draft
tarockey wants to merge 4 commits intomicrosoft:mainfrom
tarockey:feature/dotnet-workflow-agent-message-filters
Draft

.NET: DRAFT feat: Implement message filtering to exclude non-portable content typ…#5410
tarockey wants to merge 4 commits intomicrosoft:mainfrom
tarockey:feature/dotnet-workflow-agent-message-filters

Conversation

@tarockey
Copy link
Copy Markdown

This is a draft PR, happy to close it or modify based on discussion.

Motivation and Context

Closes #5338

When a Foundry server-side agent with server-side tools (MCP, web search, code interpreter, Fabric Data Agent, etc.) runs as part of a multi-agent workflow, the Responses API returns output-only items like mcp_list_tools, reasoning, web_search_call, and fabric_dataagent_preview_call in its response. These items were being forwarded as-is to the next agent in the workflow via context.SendMessageAsync(response.Messages...).

When the receiving agent sent its own Responses API request, the Microsoft.Extensions.AI library round-tripped these items via ChatMessage.RawRepresentation, including them as input items. The Responses API rejected the request with invalid_payload / invalid_request_error because these are output-only item types that are not valid as input.

This makes multi-agent workflows involving Foundry server-side agents with server-side tools unusable.

Description

Adds FilterForwardableMessages to AIAgentHostExecutor that sanitizes response messages before forwarding them into the workflow. The filter does two things:

  1. Strips non-portable content: Only content items with recognized portable types (TextContent, DataContent, UriContent, FunctionCallContent, FunctionResultContent, ToolApprovalRequestContent, ToolApprovalResponseContent, HostedFileContent, ErrorContent) are retained. Server-side artifacts like reasoning tokens and provider-specific tool call items are dropped.

  2. Strips RawRepresentation: New ChatMessage objects are constructed without the provider-specific RawRepresentation, preventing the Microsoft.Extensions.AI library from round-tripping the original output-only items (e.g., mcp_list_tools) as input to the next agent.

Messages that contain zero portable content items after filtering are dropped entirely.

What is NOT affected:

  • Streaming AgentResponseUpdates yielded via YieldOutputAsync — these fire before the filter and retain full fidelity
  • AgentResponse events emitted via EmitAgentResponseEvents — unaffected
  • DurableTask checkpoint serialization — RawRepresentation is [JsonIgnore] and was never persisted
  • AIContent.RawRepresentation on individual content items — preserved; only the message-level RawRepresentation is stripped

Known follow-up: HandoffAgentExecutor has a similar unfiltered path at sharedState.Conversation.AddMessages(result.Response.Messages) that would need an equivalent fix if Foundry server-side agents participate in handoff workflows.

Contribution Checklist

  • The code builds clean without any errors or warnings
  • The PR follows the Contribution Guidelines
  • All unit tests pass, and I have added new tests where possible
  • Is this a breaking change? If yes, add "[BREAKING]" prefix to the title of the PR.

tarockey and others added 2 commits April 21, 2026 12:03
…es before forwarding

Co-authored-by: Copilot <copilot@github.com>
… executors

Co-authored-by: Copilot <copilot@github.com>
Copilot AI review requested due to automatic review settings April 21, 2026 19:59
@moonbox3 moonbox3 added .NET workflows Related to Workflows in agent-framework labels Apr 21, 2026
@github-actions github-actions Bot changed the title DRAFT feat: Implement message filtering to exclude non-portable content typ… .NET: DRAFT feat: Implement message filtering to exclude non-portable content typ… Apr 21, 2026
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR addresses a .NET Workflows interoperability bug where provider-specific, output-only Responses API items (e.g., reasoning/tool call artifacts) were being forwarded between agents and then round-tripped as invalid input, causing invalid_payload errors when chaining Foundry-backed agents.

Changes:

  • Added a message sanitization step in AIAgentHostExecutor to forward only “portable” AIContent types and to strip ChatMessage.RawRepresentation.
  • Dropped messages that become empty after content filtering (at the message level).
  • Added unit tests covering filtering of non-portable content and stripping of RawRepresentation.

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated 4 comments.

File Description
dotnet/src/Microsoft.Agents.AI.Workflows/Specialized/AIAgentHostExecutor.cs Filters forwarded response messages to remove output-only artifacts and strips message-level RawRepresentation before forwarding.
dotnet/tests/Microsoft.Agents.AI.Workflows.UnitTests/AIAgentHostExecutorTests.cs Adds tests validating content filtering behavior and RawRepresentation stripping for forwarded messages.

Comment thread dotnet/src/Microsoft.Agents.AI.Workflows/Specialized/AIAgentHostExecutor.cs Outdated
Comment thread dotnet/src/Microsoft.Agents.AI.Workflows/Specialized/AIAgentHostExecutor.cs Outdated
AuthorName = message.AuthorName,
MessageId = message.MessageId,
CreatedAt = message.CreatedAt,
AdditionalProperties = message.AdditionalProperties,
Copy link

Copilot AI Apr 21, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

AdditionalProperties = message.AdditionalProperties copies the dictionary by reference into the forwarded ChatMessage. Since AdditionalPropertiesDictionary is mutable and is cloned elsewhere when merging (e.g., MessageMerger uses new(current)), this can unintentionally couple mutations between the original response message and the forwarded message. Consider cloning the dictionary (or omitting provider-specific entries) when constructing the sanitized message.

Suggested change
AdditionalProperties = message.AdditionalProperties,
AdditionalProperties = message.AdditionalProperties is null ? null : new(message.AdditionalProperties),

Copilot uses AI. Check for mistakes.
Comment on lines +434 to +439
// Assert: no ChatMessage lists should be forwarded (only TurnToken)
List<ChatMessage> forwardedMessages = testContext.QueuedMessages[executor.Id]
.Select(e => e.Message)
.OfType<List<ChatMessage>>()
.SelectMany(list => list)
.ToList();
Copy link

Copilot AI Apr 21, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This test’s assertion comment says “no ChatMessage lists should be forwarded (only TurnToken)”, but AIAgentHostExecutor forwards incoming messages by default (AIAgentHostOptions.ForwardIncomingMessages = true), so a List<ChatMessage> (often empty) can still be sent before the response forwarding. To make the test accurately validate “no forwarded chat messages”, consider either setting ForwardIncomingMessages = false for this test or asserting on the envelopes (e.g., that no non-empty List<ChatMessage> was sent).

Copilot uses AI. Check for mistakes.
…stExecutor.cs

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
…stExecutor.cs

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

.NET workflows Related to Workflows in agent-framework

Projects

Status: No status

Development

Successfully merging this pull request may close these issues.

.NET: [Bug]: Foundry Responses API based agents fail in Workflows when Foundry hosted tools are used.

4 participants