-
Notifications
You must be signed in to change notification settings - Fork 1.3k
Description
Description
We first observed this when testing Durable Agents with the GitHubCopilot agent type. Upon further analysis, however, this appears to be a broader .NET serialization bug.
When a ChatMessage contains a plain AIContent instance whose only useful data is in RawRepresentation, shared JSON serialization paths can serialize that content as an empty object {}.
This is the same underlying behavior that likely surfaces in #4481 for Durable Agents, but it also reproduces outside Durable Agents.
Simplified example
I reproduced the same behavior through ChatClientAgentContinuationToken, which serializes inputMessages using AgentJsonUtilities.DefaultOptions:
dotnet/src/Microsoft.Agents.AI/ChatClient/ChatClientAgentContinuationToken.cs:42-47dotnet/src/Microsoft.Agents.AI/AgentJsonUtilities.cs
A ChatMessage containing:
new ChatMessage(ChatRole.Assistant,
[
new AIContent
{
RawRepresentation = new { kind = "toolCall", name = "read_file", callId = "abc123" }
}
])round-trips to JSON like:
{"role":"assistant","contents":[{}]}I also noticed that workflows use a similar shared ChatMessage serialization path in WorkflowsJsonUtilities:
dotnet/src/Microsoft.Agents.AI.Workflows/WorkflowsJsonUtilities.cs:36-40
So the underlying issue seems to be that opaque/base AIContent loses debuggable structure when serialized through shared JSON utilities, and the information is lost when deserializing the saved conversation state.
GitHub Copilot agent context
This is especially relevant for GitHub Copilot agents, which appear to rely on this pattern today.
In GitHubCopilotAgent, some assistant-side events are converted into plain AIContent instances with only RawRepresentation populated instead of being projected to richer content types:
dotnet/src/Microsoft.Agents.AI.GitHub.Copilot/GitHubCopilotAgent.cs:349-356dotnet/src/Microsoft.Agents.AI.GitHub.Copilot/GitHubCopilotAgent.cs:418-426
Specifically:
AssistantMessageEventis wrapped asnew AIContent { RawRepresentation = assistantMessage }SessionEventis wrapped asnew AIContent { RawRepresentation = sessionEvent }
Expected Behavior
One of the following:
- First party agent implementations should not use the generic
AIContenttypes. - If an
AIContentinstance cannot be projected to a richer known subtype, it should still serialize with some useful metadata instead of{}.
Examples of acceptable behavior:
- serialize a lightweight envelope with a discriminator plus metadata such as raw type / name / callId
- preserve a small subset of
RawRepresentationfor debuggability - convert known opaque content to a more explicit content type before serialization
Actual Behavior
Shared JSON serialization paths can emit contents: [{}], which loses all useful structure and makes state, logs, continuation tokens, and other persisted artifacts impossible to interpret.
Code Sample
using Microsoft.Agents.AI;
using Microsoft.Extensions.AI;
ResponseContinuationToken inner = ResponseContinuationToken.FromBytes(new byte[] { 1, 2, 3 });
ChatClientAgentContinuationToken token = new(inner)
{
InputMessages =
[
new ChatMessage(ChatRole.Assistant,
[
new AIContent
{
RawRepresentation = new { kind = "toolCall", name = "read_file", callId = "abc123" }
}
])
]
};
ChatClientAgentContinuationToken roundTrip =
ChatClientAgentContinuationToken.FromToken(ResponseContinuationToken.FromBytes(token.ToBytes()));Observed serialized shape inside the token:
{"type":"chatClientAgentContinuationToken","innerToken":"\"AQID\"","inputMessages":"[{\"role\":\"assistant\",\"contents\":[{}]}]"}Error Messages / Stack Traces
No exception is thrown. The issue is silent data loss / loss of debuggable structure during serialization.
Package Versions
Microsoft.Agents.AI.DurableTask: 1.0.0-preview.260225.1