Skip to main content

What is Group Chat?

Group Chat enables multiple agents to participate in a coordinated conversation. An orchestrator manages the conversation flow, deciding which agent speaks next based on:
  • Predefined workflows
  • LLM-based role selection
  • Round-robin scheduling
  • Custom orchestration logic

Basic Group Chat

Create a simple group chat with multiple agents:
using AutoGen.Core;
using AutoGen.OpenAI;
using AutoGen.OpenAI.Extension;
using OpenAI;

var apiKey = Environment.GetEnvironmentVariable("OPENAI_API_KEY");
var openAIClient = new OpenAIClient(apiKey);
var model = "gpt-4o-mini";

// Create agents
var coder = new OpenAIChatAgent(
    chatClient: openAIClient.GetChatClient(model),
    name: "coder",
    systemMessage: "You are a C# coder. Write code between ```csharp and ```")
    .RegisterMessageConnector()
    .RegisterPrintMessage();

var reviewer = new OpenAIChatAgent(
    chatClient: openAIClient.GetChatClient(model),
    name: "reviewer",
    systemMessage: "You review code for correctness and best practices")
    .RegisterMessageConnector()
    .RegisterPrintMessage();

var user = new DefaultReplyAgent(
    name: "user",
    defaultReply: "END")
    .RegisterPrintMessage();

// Create admin agent to orchestrate
var admin = new OpenAIChatAgent(
    chatClient: openAIClient.GetChatClient(model),
    name: "admin")
    .RegisterMessageConnector();

// Create group chat
var group = new GroupChat(
    members: [coder, reviewer, user],
    admin: admin);

// Start conversation
var chatHistory = new List<IMessage>
{
    new TextMessage(Role.User, "Write a C# function to calculate factorial")
};

var result = await group.CallAsync(chatHistory, maxRound: 10);

Orchestration Strategies

AutoGen provides multiple orchestrators to control conversation flow:

RolePlayOrchestrator

Uses an admin agent and optional workflow to select speakers:
var admin = new OpenAIChatAgent(
    chatClient: openAIClient.GetChatClient("gpt-4"),
    name: "admin",
    systemMessage: "You coordinate the team and select the next speaker")
    .RegisterMessageConnector();

var group = new GroupChat(
    members: [coder, reviewer, tester],
    admin: admin);
The admin agent:
  • Analyzes conversation history
  • Decides which agent should speak next
  • Considers the workflow if provided

WorkflowOrchestrator

Follows a predefined workflow without an admin:
using AutoGen.Core;

// Define workflow transitions
var workflow = new Graph();
workflow.AddTransition(Transition.Create(coder, reviewer));
workflow.AddTransition(Transition.Create(reviewer, user));

var group = new GroupChat(
    members: [coder, reviewer, user],
    workflow: workflow);

RoundRobinOrchestrator

Agents speak in sequential order:
var orchestrator = new RoundRobinOrchestrator();

var group = new GroupChat(
    members: [agent1, agent2, agent3],
    orchestrator: orchestrator);

Custom Orchestrator

Implement IOrchestrator for custom logic:
public class CustomOrchestrator : IOrchestrator
{
    public Task<IAgent?> GetNextSpeakerAsync(
        OrchestrationContext context,
        CancellationToken cancellationToken = default)
    {
        var lastMessage = context.ChatHistory.LastOrDefault();
        
        // Custom logic to select next speaker
        if (lastMessage?.From == "coder")
            return Task.FromResult(context.Candidates
                .FirstOrDefault(a => a.Name == "reviewer"));
        
        // Return null to end conversation
        return Task.FromResult<IAgent?>(null);
    }
}

var group = new GroupChat(
    members: [coder, reviewer],
    orchestrator: new CustomOrchestrator());

Workflow-Based Conversations

Define explicit conversation flows using graphs:
1

Create agents

var coder = new OpenAIChatAgent(
    chatClient: openAIClient.GetChatClient(model),
    name: "coder",
    systemMessage: "You write C# code")
    .RegisterMessageConnector()
    .RegisterPrintMessage();

var commenter = new OpenAIChatAgent(
    chatClient: openAIClient.GetChatClient(model),
    name: "commenter",
    systemMessage: "You add inline comments to code")
    .RegisterMessageConnector()
    .RegisterPrintMessage();

var user = new DefaultReplyAgent("user", "END")
    .RegisterPrintMessage();
2

Define workflow

// User -> Coder -> Commenter -> User
var workflow = new Graph();
workflow.AddTransition(Transition.Create(user, coder));
workflow.AddTransition(Transition.Create(coder, commenter));
workflow.AddTransition(Transition.Create(commenter, user));
3

Create group chat

var group = new GroupChat(
    members: [coder, commenter, user],
    workflow: workflow);
4

Run conversation

var instruction = new TextMessage(
    Role.User,
    @"Workflow:
    User asks question -> Coder writes code -> 
    Commenter adds comments -> User confirms");

var question = new TextMessage(
    Role.User,
    "Write a function to check if a number is prime");

var history = new List<IMessage> { instruction, question };
var result = await group.CallAsync(history, maxRound: 10);

Dynamic Group Chat Example

Complete example with Semantic Kernel integration:
using AutoGen.Core;
using AutoGen.OpenAI;
using AutoGen.OpenAI.Extension;
using AutoGen.SemanticKernel;
using AutoGen.SemanticKernel.Extension;
using Microsoft.SemanticKernel;
using OpenAI;

public class DynamicGroupChatExample
{
    public static async Task RunAsync()
    {
        var apiKey = Environment.GetEnvironmentVariable("OPENAI_API_KEY");
        var model = "gpt-4o-mini";
        var openAIClient = new OpenAIClient(apiKey);

        // Create coder with OpenAI
        var coder = new OpenAIChatAgent(
            chatClient: openAIClient.GetChatClient(model),
            name: "coder",
            systemMessage: @"
                You are a C# coder.
                When writing code, put it between ```csharp and ```
            ")
            .RegisterMessageConnector()
            .RegisterPrintMessage();

        // Create commenter with Semantic Kernel
        var kernel = Kernel
            .CreateBuilder()
            .AddOpenAIChatCompletion(modelId: model, apiKey: apiKey)
            .Build();
            
        var commenter = new SemanticKernelAgent(
            kernel: kernel,
            name: "commenter",
            systemMessage: @"
                You write inline comments for code.
                Add unit tests if necessary.
            ")
            .RegisterMessageConnector()
            .RegisterPrintMessage();

        // Create user proxy
        var userProxy = new DefaultReplyAgent("user", defaultReply: "END")
            .RegisterPrintMessage();

        // Create admin for orchestration
        var admin = new OpenAIChatAgent(
            chatClient: openAIClient.GetChatClient(model),
            name: "admin")
            .RegisterMessageConnector();

        // Create group
        var group = new GroupChat(
            members: [coder, commenter, userProxy],
            admin: admin);

        // Define workflow instruction
        var workflowInstruction = new TextMessage(
            Role.User,
            @"
            Workflow:
            User asks question -> Coder writes code
            Coder writes code -> Commenter adds comments
            Commenter adds comments -> User ends with END
            ");

        var question = new TextMessage(
            Role.User,
            "How to calculate the 100th Fibonacci number?");

        var chatHistory = new List<IMessage> { workflowInstruction, question };
        
        // Run group chat
        while (true)
        {
            var replies = await group.CallAsync(chatHistory, maxRound: 1);
            var lastReply = replies.Last();
            chatHistory.Add(lastReply);

            if (lastReply.From == userProxy.Name)
            {
                break;
            }
        }

        // Summarize conversation
        var summary = await coder.SendAsync(
            "Summarize the conversation",
            chatHistory: chatHistory);
    }
}

Initialize Messages

Provide context that persists across conversations:
var contextMessages = new List<IMessage>
{
    new TextMessage(Role.System, "You are working on a financial application"),
    new TextMessage(Role.System, "Follow company coding standards"),
    new TextMessage(Role.System, "All currency calculations use decimal type")
};

var group = new GroupChat(
    members: agents,
    admin: admin,
    initializeMessages: contextMessages);

Managing Conversation Flow

Termination

End conversations gracefully:
// Terminate by message content
var message = TextMessage.CreateGroupChatTerminateMessage("Task completed");

// Check if message is termination
if (message.IsGroupChatTerminateMessage())
{
    Console.WriteLine("Conversation ended");
}

Max Rounds

Limit conversation length:
// Stop after 10 agent responses
var result = await group.CallAsync(chatHistory, maxRound: 10);

Manual Control

Step through conversation manually:
var history = new List<IMessage> { initialMessage };

for (int i = 0; i < maxRounds; i++)
{
    var replies = await group.CallAsync(history, maxRound: 1);
    var lastReply = replies.Last();
    
    Console.WriteLine($"Round {i + 1}: {lastReply.From} said: {lastReply.GetContent()}");
    
    history.Add(lastReply);
    
    if (ShouldTerminate(lastReply))
    {
        break;
    }
}

Advanced Patterns

Nested Group Chats

Create hierarchical agent structures:
// Backend team
var backendGroup = new GroupChat(
    members: [backendDev, dbExpert],
    admin: backendLead);

// Frontend team
var frontendGroup = new GroupChat(
    members: [frontendDev, uiDesigner],
    admin: frontendLead);

// Main project group
var projectGroup = new GroupChat(
    members: [backendLead, frontendLead, projectManager],
    admin: architect);

Conditional Workflows

Branching logic based on message content:
public class ConditionalOrchestrator : IOrchestrator
{
    public Task<IAgent?> GetNextSpeakerAsync(
        OrchestrationContext context,
        CancellationToken cancellationToken = default)
    {
        var lastMessage = context.ChatHistory.LastOrDefault();
        var content = lastMessage?.GetContent()?.ToLower();

        // Route based on keywords
        if (content?.Contains("bug") == true)
            return Task.FromResult(context.Candidates
                .FirstOrDefault(a => a.Name == "debugger"));
        
        if (content?.Contains("test") == true)
            return Task.FromResult(context.Candidates
                .FirstOrDefault(a => a.Name == "tester"));
        
        if (content?.Contains("deploy") == true)
            return Task.FromResult(context.Candidates
                .FirstOrDefault(a => a.Name == "devops"));

        return Task.FromResult<IAgent?>(null);
    }
}

Priority-Based Selection

public class PriorityOrchestrator : IOrchestrator
{
    private readonly Dictionary<string, int> _priorities;

    public PriorityOrchestrator(Dictionary<string, int> priorities)
    {
        _priorities = priorities;
    }

    public Task<IAgent?> GetNextSpeakerAsync(
        OrchestrationContext context,
        CancellationToken cancellationToken = default)
    {
        // Select agent with highest priority that hasn't spoken recently
        var recentSpeakers = context.ChatHistory
            .TakeLast(3)
            .Select(m => m.From)
            .ToHashSet();

        var nextAgent = context.Candidates
            .Where(a => !recentSpeakers.Contains(a.Name))
            .OrderByDescending(a => _priorities.GetValueOrDefault(a.Name, 0))
            .FirstOrDefault();

        return Task.FromResult(nextAgent);
    }
}

Best Practices

  • Give each agent a clear, specific role
  • Avoid overlapping responsibilities
  • Use descriptive names (e.g., “coder”, “reviewer”, “tester”)
  • Document roles in system messages
  • Keep workflows simple and linear when possible
  • Use admin-based orchestration for complex logic
  • Provide clear workflow instructions in initial messages
  • Test workflows with different scenarios
  • Set reasonable maxRound limits
  • Use DefaultReplyAgent for simple responses
  • Cache responses when appropriate
  • Monitor token usage across all agents
try
{
    var result = await group.CallAsync(history, maxRound: 10);
}
catch (Exception ex)
{
    Console.WriteLine($"Group chat error: {ex.Message}");
    // Implement retry or fallback logic
}

Next Steps

Function Calling

Add tools to group chat agents

Code Execution

Execute code in group conversations

Agents

Learn more about agent types

Examples

See complete group chat examples