AutoGen GroupChat: Coordinating Multiple AI Agents
Modern AI applications increasingly demand collaboration between multiple specialized agents. AutoGen's GroupChat feature provides a powerful framework for orchestrating conversations where several AI agents—each with distinct roles, capabilities, and LLM backends—work together to solve complex problems. This tutorial walks you through everything you need to know to build, configure, and deploy multi-agent group chats effectively.
What is AutoGen GroupChat?
AutoGen GroupChat is a conversation pattern within Microsoft's AutoGen framework that allows multiple AI agents to participate in a shared, dynamic conversation. Unlike simple two-agent chats or rigid sequential workflows, GroupChat creates a collaborative space where agents can jump in, respond to each other, and collectively reason about a task. A GroupChatManager orchestrates the flow, deciding which agent speaks next based on the conversation context and a configurable speaker selection strategy.
Key characteristics include:
- Shared message context — All agents see the full conversation history, enabling informed contributions
- Dynamic speaker selection — The manager routes turns intelligently rather than following a fixed order
- Role specialization — Each agent can have a distinct system prompt, tools, and LLM configuration
- Custom routing logic — You can inject your own selector function to control who speaks when
Why GroupChat Matters for Multi-Agent Coordination
Single-agent systems often hit limits when tasks require diverse expertise, cross-validation, or creative brainstorming. GroupChat addresses these challenges directly:
- Division of labor — Assign a coding agent, a testing agent, and a reviewer agent that critique each other's outputs in real time
- Parallel perspectives — Let a creative agent and an analytical agent propose solutions simultaneously, then have a moderator synthesize them
- Error reduction — Multiple agents can catch mistakes that a single agent might overlook
- Complex workflows — Build systems where agents hand off subtasks naturally without brittle orchestration code
In practice, teams use GroupChat for code generation with review cycles, document authoring with collaborative editing, research synthesis from multiple sources, and customer support triage where different agents handle different issue types.
Core Concepts and Architecture
Understanding the architecture helps you design better group chats. The system consists of:
- Agents — Instances of
ConversableAgent(or subclasses likeAssistantAgent,UserProxyAgent) that participate in the conversation. Each agent has a name, system message, LLM configuration, and optional tools. - GroupChat — A container object that holds the list of participating agents, the messages exchanged, and configuration for speaker selection.
- GroupChatManager — A special agent that manages the conversation flow. It receives messages, consults the speaker selection strategy, and routes the next turn to the chosen agent.
- Speaker Selection Strategy — The logic (default or custom) that determines which agent speaks next. The default strategy uses an LLM-based selector that reads the conversation and picks the most appropriate agent.
The conversation proceeds in turns: an agent generates a message, the manager receives it, the manager selects the next speaker, and the cycle repeats until a termination condition is met (like a specific phrase, a message count limit, or a custom stopping criterion).
Setting Up Your Environment
Before building your first group chat, install the required packages. AutoGen works with various LLM providers; we'll use OpenAI in these examples, but the patterns apply universally.
pip install autogen-agentchat autogen-ext[openai] python-dotenv
Create a .env file or export your API key directly. For production, always use environment variables:
# .env file
OPENAI_API_KEY=your-api-key-here
Now import the core components you'll need throughout this tutorial:
import os
import asyncio
from dotenv import load_dotenv
from autogen_agentchat.agents import AssistantAgent
from autogen_agentchat.conditions import MaxMessageTermination, TextMentionTermination
from autogen_agentchat.messages import TextMessage
from autogen_agentchat.teams import GroupChat, GroupChatManager
from autogen_agentchat.ui import Console
from autogen_ext.models.openai import OpenAIChatCompletionClient
load_dotenv()
Building Your First GroupChat
Let's start with a simple three-agent group chat: a planner, a coder, and a reviewer. This pattern mirrors real-world software development workflows.
Step 1: Define your model client. All agents share a model client (though you can give different agents different clients for cost optimization).
model_client = OpenAIChatCompletionClient(
model="gpt-4o-mini",
temperature=0.7,
)
Step 2: Create the agents with distinct system messages that define their roles and personalities:
planner_agent = AssistantAgent(
name="Planner",
system_message="""You are a strategic planner. Your role is to:
1. Analyze the user's request and break it into concrete steps
2. Outline a clear plan before any code is written
3. Identify edge cases and potential pitfalls
4. Coordinate with the Coder and Reviewer agents
When you receive a task, always provide a structured plan first.
Do not write implementation code yourself—delegate that to the Coder.""",
model_client=model_client,
)
coder_agent = AssistantAgent(
name="Coder",
system_message="""You are an expert Python developer. Your role is to:
1. Implement solutions based on the Planner's specifications
2. Write clean, well-documented code with proper error handling
3. Include inline comments explaining complex logic
4. Provide complete, runnable code blocks (not fragments)
Always wrap your code in python blocks and explain your implementation choices.""",
model_client=model_client,
)
reviewer_agent = AssistantAgent(
name="Reviewer",
system_message="""You are a code reviewer and quality assurance specialist. Your role is to:
1. Review code from the Coder for correctness, security, and performance
2. Suggest improvements and catch bugs
3. Verify that the implementation matches the Planner's spec
4. If you find issues, be specific about what needs to change
Be constructive and precise. If the code is good, explicitly approve it.""",
model_client=model_client,
)
Step 3: Configure termination conditions. Without these, the conversation could loop indefinitely. Common strategies include stopping after a maximum number of messages or when a specific phrase appears:
termination_conditions = [
MaxMessageTermination(max_messages=15),
TextMentionTermination("FINAL_APPROVAL"),
]
Step 4: Create the GroupChat and its manager, then run it:
group_chat = GroupChat(
participants=[planner_agent, coder_agent, reviewer_agent],
max_round=10,
termination_condition=termination_conditions,
)
chat_manager = GroupChatManager(
group_chat=group_chat,
)
async def run_demo():
task = """
Write a Python function that takes a list of integers and returns
the two numbers that sum to a target value. Include edge case handling
for empty lists and lists with no valid pair.
"""
await Console(
chat_manager.run_stream(
task=task,
)
)
asyncio.run(run_demo())
When you run this, you'll see the Planner outline a strategy, the Coder implement it, and the Reviewer critique the result. The conversation flows naturally because the default speaker selection LLM reads the context and routes turns appropriately.
Configuring Agent Roles and Behaviors
The quality of your group chat depends heavily on how you craft agent system messages. Here are proven patterns for common agent archetypes:
The Moderator / Facilitator Agent — keeps the conversation on track, summarizes progress, and resolves deadlocks:
moderator_agent = AssistantAgent(
name="Moderator",
system_message="""You are a neutral facilitator. Your responsibilities:
- At the start of each round, briefly summarize what has been accomplished so far
- If two agents disagree, help them find common ground by reframing the issue
- If the conversation goes off-topic, gently redirect it
- You may call on specific agents by name when their input is needed
- Keep contributions concise—aim for progress, not perfection in every message""",
model_client=model_client,
)
The Tool-Using Agent — equipped with function tools for data retrieval or computation:
from autogen_agentchat.agents import AssistantAgent
from autogen_ext.tools import FunctionTool
async def search_database(query: str) -> str:
# Simulated database search
results = {
"revenue_q3": "Q3 revenue: $2.4M, up 12% YoY",
"churn_rate": "Current churn rate: 3.2%, target: under 2%",
}
return results.get(query.lower(), f"No data found for '{query}'")
tool_agent = AssistantAgent(
name="DataAnalyst",
system_message="Use the search_database tool to retrieve metrics. Analyze data and provide insights.",
model_client=model_client,
tools=[FunctionTool(search_database, name="search_database")],
)
The Human-in-the-Loop Agent — represents a human user who can provide input when the AI agents get stuck:
from autogen_agentchat.agents import UserProxyAgent
human_proxy = UserProxyAgent(
name="HumanUser",
description="A human operator who can approve final outputs and provide domain expertise",
input_func=input, # Uses console input; replace with your custom input function as needed
)
When including a UserProxyAgent in a GroupChat, the manager will route to it when human judgment is needed, and the conversation pauses for actual human input before continuing.
Custom Selector Functions for Message Routing
The default speaker selection uses an LLM call to decide who speaks next. This works well but can be slow and costly for high-throughput scenarios. AutoGen allows you to replace this with a custom selector function for deterministic, fast routing.
A custom selector is a function that takes the conversation messages and agent list, and returns the name of the next agent. Here's a round-robin selector:
def round_robin_selector(messages, agents):
"""
Cycles through agents in order: Planner -> Coder -> Reviewer -> Planner ...
Skips the agent that just spoke to avoid self-repetition.
"""
# Determine who spoke last by checking the most recent message's source
if messages:
last_speaker = messages[-1].source
else:
last_speaker = None
# Build an ordered list of agent names (excluding the manager itself)
agent_names = [agent.name for agent in agents if agent.name != "GroupChatManager"]
if last_speaker and last_speaker in agent_names:
last_index = agent_names.index(last_speaker)
next_index = (last_index + 1) % len(agent_names)
else:
next_index = 0
return agent_names[next_index]
# Use the custom selector when creating the GroupChat
group_chat = GroupChat(
participants=[planner_agent, coder_agent, reviewer_agent],
max_round=10,
termination_condition=termination_conditions,
selector_func=round_robin_selector,
)
For more sophisticated routing, you can implement keyword-based or state-machine selectors:
def keyword_router(messages, agents):
"""
Routes based on keywords in the last message.
- Messages containing 'PLAN' or 'spec' go to Planner
- Messages containing 'CODE' or 'implement' go to Coder
- Messages containing 'REVIEW' or 'check' go to Reviewer
- Otherwise falls back to round-robin.
"""
if not messages:
return agents[0].name
last_msg = messages[-1].content.lower()
agent_map = {
"planner": "Planner",
"coder": "Coder",
"reviewer": "Reviewer",
}
if "plan" in last_msg or "spec" in last_msg or "approach" in last_msg:
return agent_map["planner"]
elif "code" in last_msg or "implement" in last_msg or "write" in last_msg:
return agent_map["coder"]
elif "review" in last_msg or "check" in last_msg or "test" in last_msg:
return agent_map["reviewer"]
# Fallback: pick the first agent
return agents[0].name
Custom selectors give you precise control and eliminate the latency and cost of an extra LLM call per turn. Use them when your conversation flow is predictable.
Managing Conversation Flow with Speaker Selection
The GroupChatManager does more than just route messages—it shapes the entire conversation dynamics. Understanding its configuration options lets you fine-tune behavior:
max_round— Hard limit on conversation turns. Set this generously (e.g., 20-30) for complex tasks, but always pair it with a semantic termination condition.speaker_selection_method— Can be"auto"(LLM-based, default),"manual"(your custom selector), or"round_robin"(built-in deterministic cycling).allow_repeat_speaker— WhenFalse, prevents the same agent from speaking twice in a row, which helps avoid monologues.speaker_transitions— A dictionary mapping agent names to lists of allowed next speakers, enabling graph-based conversation flows.
Here's an example using speaker transitions to enforce a strict workflow:
group_chat = GroupChat(
participants=[planner_agent, coder_agent, reviewer_agent, moderator_agent],
max_round=20,
termination_condition=termination_conditions,
allow_repeat_speaker=False,
speaker_transitions={
"Planner": ["Coder"],
"Coder": ["Reviewer"],
"Reviewer": ["Planner", "Moderator"],
"Moderator": ["Planner", "Coder", "Reviewer"],
},
)
This configuration enforces that the Planner always hands off to the Coder, the Coder's output always goes to the Reviewer, and the Reviewer can either send improvements back to the Planner or escalate to the Moderator for a decision. The Moderator can then redirect to any agent as needed.
Best Practices for GroupChat Design
After building dozens of multi-agent systems, certain patterns consistently produce better results. Here are the most important guidelines:
1. Keep Agent Roles Narrow and Non-Overlapping
Agents with overlapping responsibilities often produce redundant messages or contradictory guidance. Define clear boundaries. If you need two coding agents, differentiate them by language (Python vs. JavaScript) or by concern (backend vs. frontend).
2. Use Strong, Specific System Messages
Vague system messages lead to vague outputs. Include explicit instructions about when an agent should speak, what format to use, and when to defer to others. For example, tell your Reviewer: "If the code meets all requirements, respond with 'FINAL_APPROVAL' and nothing else." This ties directly into your TextMentionTermination condition.
3. Implement Layered Termination Conditions
Always combine a MaxMessageTermination (safety net) with a semantic condition. A common pattern:
from autogen_agentchat.conditions import MaxMessageTermination, TextMentionTermination, OrTermination
termination = OrTermination([
MaxMessageTermination(max_messages=30),
TextMentionTermination("FINAL_APPROVAL"),
TextMentionTermination("TASK_COMPLETE"),
])
4. Monitor and Log Conversations
GroupChats can become expensive quickly. Log every turn to track costs and debug unexpected behavior:
async def run_with_logging(task):
stream = chat_manager.run_stream(task=task)
async for message in stream:
print(f"[{message.source}]: {message.content[:100]}...")
# Persist to your logging system here
5. Test with Simpler LLMs First
During development, use gpt-4o-mini or gpt-3.5-turbo to iterate quickly on your agent design and routing logic. Switch to more capable models only after the conversation structure works reliably. This saves significant costs during the design phase.
6. Handle the "Silent Agent" Problem
Sometimes an agent receives a turn but produces no useful output (empty response, or just "I agree"). Mitigate this by instructing agents to always contribute substantively: "If you have nothing to add, explicitly state what has been accomplished and suggest the next step."
Advanced Patterns: Nested Chats and Handoffs
GroupChats can contain other chat patterns. This nesting enables sophisticated workflows where, for example, a group chat spawns a focused two-agent sub-conversation for detailed code review before returning to the main discussion.
Here's how to implement a handoff pattern where an agent initiates a nested chat:
from autogen_agentchat.teams import RoundRobinGroupChat
from autogen_agentchat.agents import CodingAssistantAgent
# Create a specialized coding sub-team
code_generator = CodingAssistantAgent(
name="CodeGenerator",
system_message="Generate Python code based on specifications. Output only code.",
model_client=model_client,
)
code_tester = AssistantAgent(
name="CodeTester",
system_message="Write unit tests for the provided code. Use pytest format.",
model_client=model_client,
)
# This sub-team uses round-robin for deterministic code-test cycles
code_sub_team = RoundRobinGroupChat(
participants=[code_generator, code_tester],
max_round=4,
)
# In your main group chat, an agent can invoke this sub-team
# by returning a HandoffAction (available in newer AutoGen versions)
# or by explicitly calling the sub-team's run method in a tool
Another powerful pattern is the escalation chat. When the main group reaches an impasse, the Moderator can spawn a smaller, more focused group (or a single expert agent) to resolve the specific blocker before returning to the full group:
escalation_agent = AssistantAgent(
name="EscalationExpert",
system_message="""You are the ultimate authority. When the team is stuck:
1. Read the entire conversation history carefully
2. Identify the root cause of the disagreement
3. Make a definitive decision with clear reasoning
4. Respond with 'DECISION: [your ruling]' so the main chat can resume""",
model_client=OpenAIChatCompletionClient(model="gpt-4o", temperature=0.3),
)
Common Pitfalls and How to Avoid Them
Even experienced developers encounter these challenges. Here's how to navigate them:
Pitfall 1: Infinite Loops — Two agents keep responding to each other without making progress. Solution: Implement a staleness detector. Track whether the conversation state is actually changing between rounds. Use allow_repeat_speaker=False and set a reasonable max_round.
Pitfall 2: The "Yes-Man" Cascade — Agents simply agree with each other ("Good point, I agree with the Planner") without adding value. Solution: In system messages, explicitly forbid vacuous agreement: "Do not simply agree with previous speakers. Always contribute a unique perspective, critique, or concrete next step."
Pitfall 3: Lost Context — Very long conversations exceed context windows, causing agents to forget early decisions. Solution: Use a Moderator agent to periodically produce structured summaries. You can also implement a custom selector that injects a compressed context summary every N turns.
Pitfall 4: Runaway Costs — GroupChats with many agents and LLM-based speaker selection can burn through tokens rapidly. Solution: Use custom selector functions instead of LLM-based selection wherever possible. Consider using cheaper models for routing decisions and more capable models only for the actual agent responses.
Pitfall 5: Unclear Ownership — Tasks fall through the cracks because no agent feels responsible. Solution: Start every conversation with a Planner agent that explicitly assigns subtasks to named agents. Use the speaker_transitions feature to enforce handoff chains.
Complete Working Example: Collaborative Code Review System
Below is a complete, production-ready example combining everything we've covered. This system takes a coding task, plans it, implements it, reviews it, and iterates until approval—all with proper termination, logging, and error handling:
import asyncio
import os
from dotenv import load_dotenv
from autogen_agentchat.agents import AssistantAgent
from autogen_agentchat.conditions import MaxMessageTermination, TextMentionTermination, OrTermination
from autogen_agentchat.teams import GroupChat, GroupChatManager
from autogen_agentchat.ui import Console
from autogen_ext.models.openai import OpenAIChatCompletionClient
load_dotenv()
async def main():
# Model clients: use a cheaper model for simpler agents
standard_model = OpenAIChatCompletionClient(
model="gpt-4o-mini",
temperature=0.7,
)
reasoning_model = OpenAIChatCompletionClient(
model="gpt-4o",
temperature=0.3,
)
# Define agents
planner = AssistantAgent(
name="Planner",
system_message="""You are a senior software architect.
When given a task:
1. Analyze requirements thoroughly
2. Produce a numbered implementation plan
3. Identify at least 3 edge cases
4. Explicitly hand off to the Coder with: 'Coder, please implement step 1'
Do not write code yourself. Your output should be a clear specification.""",
model_client=standard_model,
)
coder = AssistantAgent(
name="Coder",
system_message="""You are an expert Python developer.
1. Implement exactly what the Planner specified
2. Write complete, runnable code with type hints and docstrings
3. Include error handling for all identified edge cases
4. Wrap code in python blocks
5. After your code, state: 'Reviewer, please review this implementation'
Do not review your own code—that is the Reviewer's job.""",
model_client=reasoning_model,
)
reviewer = AssistantAgent(
name="Reviewer",
system_message="""You are a thorough code reviewer.
For each implementation:
1. Check correctness against the Planner's specification
2. Verify all edge cases are handled
3. Check for security issues, performance problems, and Pythonic style
4. If you find issues: list them clearly and return to Planner for revision
5. If the code is fully correct: respond with 'FINAL_APPROVAL' and a brief summary
Be specific. Never say 'looks good' without detailed verification.""",
model_client=reasoning_model,
)
# Termination: stop after 20 messages OR on final approval
termination = OrTermination([
MaxMessageTermination(max_messages=20),
TextMentionTermination("FINAL_APPROVAL"),
])
# Custom selector: enforce Planner -> Coder -> Reviewer -> (Planner or end)
def workflow_selector(messages, agents):
if not messages:
return "Planner"
last_msg = messages[-1]
last_speaker = last_msg.source
content_lower = last_msg.content.lower() if hasattr(last_msg, 'content') else ""
agent_names = {agent.name for agent in agents}
# If Reviewer approved, stop (termination will catch it)
if "final_approval" in content_lower:
return None # Signal end
# Enforce the workflow
if last_speaker == "Planner":
return "Coder" if "Coder" in agent_names else last_speaker
elif last_speaker == "Coder":
return "Reviewer" if "Reviewer" in agent_names else last_speaker
elif last_speaker == "Reviewer":
return "Planner" if "Planner" in agent_names else last_speaker
else:
return "Planner"
group_chat = GroupChat(
participants=[planner, coder, reviewer],
max_round=10,
termination_condition=termination,
selector_func=workflow_selector,
allow_repeat_speaker=False,
)
manager = GroupChatManager(group_chat=group_chat)
task = """
Create a Python class called 'TemperatureConverter' that:
- Converts between Celsius, Fahrenheit, and Kelvin
- Has validation that absolute zero cannot be violated
- Includes a method to convert from any scale to any other scale
- Raises meaningful custom exceptions for invalid inputs
"""
print("Starting collaborative code review GroupChat...")
print("-" * 50)
stream = manager.run_stream(task=task)
await Console(stream)
if __name__ == "__main__":
asyncio.run(main())
This example demonstrates a complete, self-contained system. The workflow selector enforces a clean plan-implement-review cycle. The termination condition catches the reviewer's approval signal. Different models are assigned to different agents for cost optimization. Every agent has clear, non-overlapping responsibilities with explicit handoff instructions.
Conclusion
AutoGen GroupChat transforms how you build AI-powered applications by enabling genuine multi-agent collaboration. Rather than chaining API calls in brittle pipelines, you create a dynamic conversation space where specialized agents reason together, critique each other's work, and converge on high-quality solutions. The framework gives you complete control—from agent personalities and tool access to custom routing logic and termination conditions—while handling the complex orchestration underneath. Start with simple two or three-agent setups, iterate on your system messages until the conversation flows naturally, then layer in custom selectors and nested chats as your requirements grow. The patterns and complete examples in this tutorial provide a foundation for building sophisticated multi-agent systems that are robust, cost-effective, and genuinely collaborative.