microsoft/autogen · error · InvalidOperationException
system message has already been set and only one system mess
Error message
system message has already been set and only one system message is supported. "system" role for input messages in the Message
What it means
AutoGen.Anthropic's AnthropicClientAgent.BuildMessages throws InvalidOperationException when an input message contains a ChatMessage whose Role == "system". The agent already injects the system message (from its systemMessage parameter) at the top of the Anthropic request, and the Anthropic API allows only one system prompt, so a second system-role message in the conversation history is rejected client-side.
Source
Thrown at dotnet/src/AutoGen.Anthropic/Agent/AnthropicClientAgent.cs:90
Tools = _tools?.ToList(),
ToolChoice = _toolChoice ?? (_tools is { Length: > 0 } ? ToolChoice.Auto : null),
StopSequences = options?.StopSequence?.ToArray(),
};
chatCompletionRequest.Messages = BuildMessages(messages);
return chatCompletionRequest;
}
private List<ChatMessage> BuildMessages(IEnumerable<IMessage> messages)
{
List<ChatMessage> chatMessages = new();
foreach (IMessage? message in messages)
{
switch (message)
{
case IMessage<ChatMessage> chatMessage when chatMessage.Content.Role == "system":
throw new InvalidOperationException(
"system message has already been set and only one system message is supported. \"system\" role for input messages in the Message");
case IMessage<ChatMessage> chatMessage:
chatMessages.Add(chatMessage.Content);
break;
default:
throw new ArgumentException($"Unexpected message type: {message?.GetType()}");
}
}
// merge messages with the same role
// fixing #2884
var mergedMessages = chatMessages.Aggregate(new List<ChatMessage>(), (acc, message) =>
{
if (acc.Count > 0 && acc.Last().Role == message.Role)
{
acc.Last().Content.AddRange(message.Content);View on GitHub (pinned to 027ecf0a37)
Solutions
- Remove system-role messages from the input list; put the instruction in the agent's systemMessage constructor parameter instead.
- If you aggregate history, filter before sending: messages.Where(m => !(m is IMessage<ChatMessage> c && c.Content.Role == "system")).
- In multi-agent flows, have other agents speak as "user"/"assistant" (or AutoGen TextMessage), never "system".
- Upgrade AutoGen.Anthropic if a newer version tolerates/merges system messages instead of throwing.
Example fix
// before
var agent = new AnthropicClientAgent(client, "assistant", systemMessage: "You are helpful.");
await agent.SendAsync(new MessageEnvelope<ChatMessage>(new ChatMessage { Role = "system", Content = [new TextContent { Text = "Always answer briefly." }] })); // throws
// after
var agent = new AnthropicClientAgent(client, "assistant", systemMessage: "You are helpful. Always answer briefly.");
await agent.SendAsync(new MessageEnvelope<ChatMessage>(new ChatMessage { Role = "user", Content = [new TextContent { Text = "hi" }] })); Defensive patterns
Strategy: type-guard
Validate before calling
var hasSystem = messages.OfType<IMessage<ChatMessage>>().Any(m => m.Content.Role == "system");
if (hasSystem)
{
messages = messages.Where(m => !(m is IMessage<ChatMessage> c && c.Content.Role == "system"));
// fold the text into the agent's systemMessage instead
} Type guard
static bool ContainsSystemMessage(IEnumerable<IMessage> messages) =>
messages.OfType<IMessage<ChatMessage>>().Any(m => m.Content.Role == "system"); Try / catch
try { await agent.SendAsync(messages); } catch (InvalidOperationException ex) when (ex.Message.Contains("system message")) { messages = messages.Where(m => m is not IMessage<ChatMessage> c || c.Content.Role != "system"); await agent.SendAsync(messages); } Prevention
- Always pass the system prompt via AnthropicClientAgent's systemMessage parameter; never inline system-role messages in history.
- When persisting conversation logs for replay, tag the initial system prompt so replay code can strip it.
- In group chats, use user/assistant roles for agent-to-agent traffic.
When it happens
Trigger: Sending an IMessage<ChatMessage> with Content.Role == "system" in the messages enumerable to AnthropicClientAgent (directly or via a group chat that replays history); using RegisterMessageConnector and re-sending the agent's own stored chat history that includes a system entry; two agents in a group chat where one emits system-role messages.
Common situations: Porting OpenAI sample code to Anthropic while keeping system messages inline in the message list; group-chat middleware that prefixes every turn with a system instruction; replaying a persisted conversation log that recorded the initial system prompt as a regular message.
Related errors
- Unexpected message type: {message?.GetType()}
- Failed to deserialize response
- Unknown content type
- Value was null.
- Unable to convert "{value}" to enum {typeToConvert}.
AI-assisted analysis of microsoft/autogen@027ecf0a37 (2026-08-15).
Data as JSON: /api/errors/abaea475475129dc.
Report an issue: GitHub.