n4nAI

AutoGen GroupChatManager configuration explained

Understand AutoGen GroupChatManager configuration — speaker selection, termination conditions, and message routing for multi-agent workflows.

n4n Team4 min read937 words

Audio narration

Coming soon — every post will get a voice note here.

AutoGen’s GroupChatManager orchestrates multi-agent conversations by controlling speaker selection, enforcing termination conditions, and routing messages between agents. It sits above individual agents and implements the conversation flow logic that single-agent patterns cannot express. Understanding its configuration options is essential for building reliable multi-agent systems that don’t stall, loop, or exceed token budgets.

What GroupChatManager actually does

The GroupChatManager is not an agent itself — it’s a conversation controller. When you create a group chat, you pass a list of agents and a GroupChatManager instance. The manager receives every message, decides which agent speaks next, checks termination criteria, and handles the mechanics of message passing.

from autogen import GroupChat, GroupChatManager, AssistantAgent, UserProxyAgent

agents = [assistant, coder, reviewer, user_proxy]
groupchat = GroupChat(agents=agents, messages=[], max_round=10)
manager = GroupChatManager(groupchat=groupchat, llm_config=llm_config)

user_proxy.initiate_chat(manager, message="Build a REST API with tests")

The manager’s llm_config drives speaker selection when you use the default auto selection method. It sends the conversation history to the configured LLM with a system prompt that asks which agent should speak next. The LLM responds with an agent name, and the manager invokes that agent.

Speaker selection methods

AutoGen provides three built-in speaker selection strategies. The choice fundamentally shapes how your conversation unfolds.

Auto selection (LLM-driven)

This is the default. The manager prompts an LLM to choose the next speaker based on conversation history. It works well for open-ended collaboration but introduces latency and non-determinism.

manager = GroupChatManager(
    groupchat=groupchat,
    llm_config={"model": "gpt-4", "temperature": 0.1},
    speaker_selection_method="auto"
)

The temperature matters here. Lower values make selection more deterministic. The system prompt includes agent names and descriptions, so write clear descriptions:

coder = AssistantAgent(
    name="coder",
    system_message="You write Python code. Output only code blocks.",
    description="Writes implementation code. Call when the task requires new code or modifications."
)

Round-robin selection

Agents speak in a fixed cyclic order. Predictable, zero LLM overhead, useful for structured workflows like “planner → coder → reviewer → tester”.

manager = GroupChatManager(
    groupchat=groupchat,
    speaker_selection_method="round_robin"
)

Order follows the agent list passed to GroupChat. If you need a different order, reorder the list or use a custom function.

Custom selection function

For deterministic logic with conditions, pass a callable. It receives the last speaker and the agent list, returns the next agent.

def select_next_speaker(last_speaker, agents):
    name = last_speaker.name if last_speaker else None
    
    if name == "planner":
        return next(a for a in agents if a.name == "coder")
    elif name == "coder":
        return next(a for a in agents if a.name == "reviewer")
    elif name == "reviewer":
        # Route back to coder if issues found, else to tester
        last_msg = groupchat.messages[-1]["content"] if groupchat.messages else ""
        if "ISSUES:" in last_msg:
            return next(a for a in agents if a.name == "coder")
        return next(a for a in agents if a.name == "tester")
    elif name == "tester":
        return next(a for a in agents if a.name == "planner")
    return agents[0]  # default to planner

manager = GroupChatManager(
    groupchat=groupchat,
    speaker_selection_method=select_next_speaker
)

This pattern eliminates LLM selection latency and gives you explicit control over flow. It’s the right choice for production pipelines where you need reproducibility.

Termination conditions

The group chat stops when any termination condition is met. Misconfigured termination is the most common cause of runaway conversations.

Max rounds

max_round in GroupChat limits total message exchanges. One round = one message from any agent. Set this as a safety ceiling.

groupchat = GroupChat(agents=agents, messages=[], max_round=20)

Max tokens

The manager can terminate when the conversation approaches a token budget. This requires llm_config with a model that supports token counting.

manager = GroupChatManager(
    groupchat=groupchat,
    llm_config={"model": "gpt-4", "max_tokens": 4000},
    max_token_limit=3500  # leave headroom for final response
)

Custom termination function

For semantic stopping criteria, pass a function that inspects the message history.

def should_terminate(messages):
    if not messages:
        return False
    last_msg = messages[-1]
    content = last_msg.get("content", "")
    
    # Stop when reviewer approves
    if last_msg.get("name") == "reviewer" and "APPROVED" in content:
        return True
    
    # Stop on explicit user termination
    if "TERMINATE" in content:
        return True
    
    # Stop after 3 consecutive coder messages (likely stuck)
    recent_speakers = [m.get("name") for m in messages[-3:]]
    if recent_speakers.count("coder") == 3:
        return True
    
    return False

groupchat = GroupChat(
    agents=agents,
    messages=[],
    max_round=30,
    termination_condition=should_terminate
)

The function receives the full message list. Return True to stop. Combine with max_round as a backstop.

Message routing and context management

By default, every agent sees every message. This doesn’t scale. Use send_message overrides and context filtering to control visibility.

Selective message sending

Agents can target specific recipients instead of broadcasting.

class TargetedAgent(AssistantAgent):
    def send(self, message, recipient, request_reply=True, silent=False):
        # Only send to specified recipient
        return super().send(message, recipient, request_reply, silent)

coder = TargetedAgent(name="coder", ...)
reviewer = TargetedAgent(name="reviewer", ...)

# In custom selection function, route explicitly
def select_next_speaker(last_speaker, agents):
    if last_speaker.name == "coder":
        # Coder sends directly to reviewer
        coder.send(coder.last_message(), reviewer)
        return reviewer
    return agents[0]

Context window management

Long conversations overflow context. Implement a summarization agent that periodically condenses history.

summarizer = AssistantAgent(
    name="summarizer",
    system_message="Summarize the conversation in 200 words. Preserve decisions and open issues."
)

def maybe_summarize(messages):
    if len(messages) > 15 and len(messages) % 5 == 0:
        # Insert summary as a system message
        summary_task = "Summarize the conversation so far."
        summary = summarizer.generate_reply(messages=[{"role": "user", "content": summary_task}])
        return [{"role": "system", "content": f"CONVERSATION SUMMARY: {summary}"}] + messages[-10:]
    return messages

# Wrap the manager's message handling
original_step = manager._process_received_message
def wrapped_step(message, sender):
    groupchat.messages = maybe_summarize(groupchat.messages)
    return original_step(message, sender)
manager._process_received_message = wrapped_step

This keeps the active context small while preserving institutional knowledge.

Human-in-the-loop integration

UserProxyAgent enables human intervention. Configure it correctly or the chat stalls waiting for input that never comes.

Automatic reply with timeout

user_proxy = UserProxyAgent(
    name="user",
    human_input_mode="TERMINATE",  # only ask human when chat tries to end
    max_consecutive_auto_reply=3,  # allow 3 auto-replies before human
    code_execution_config={"work_dir": "coding", "use_docker": False},
    default_auto_reply="Continue. If done, say TERMINATE."
)

human_input_mode options:

  • "ALWAYS" — prompt human every turn (debugging only)
  • "TERMINATE" — prompt only when termination condition triggers
  • "NEVER" — fully autonomous, uses default_auto_reply

Structured human input

For review workflows, give the human a structured choice:

def human_review_prompt(messages):
    last = messages[-1]["content"]
    print(f"\n--- Review needed ---\n{last}\n")
    print("Options: [A]pprove [R]equest changes [S]top")
    choice = input("Choice: ").strip().upper()
    if choice == "A":
        return "APPROVED"
    elif choice == "R":
        return input("Changes needed: ")
    return "TERMINATE"

user_proxy = UserProxyAgent(
    name="user",
    human_input_mode="TERMINATE",
    human_input_function=human_review_prompt
)

Common misconceptions

“GroupChatManager is an agent”

It’s not. It has no system message, no tools, no memory of its own. It only routes. If you need an agent that participates and manages, create a separate manager agent that speaks in the chat, distinct from the GroupChatManager instance.

“Auto selection understands my domain”

The selection LLM sees only agent names, descriptions, and recent messages. It doesn’t know your business logic unless you encode it in descriptions or use a custom selector. For domain-specific routing, write the selector function.

“Max_round prevents infinite loops”

It prevents infinite rounds, not infinite tokens. A single agent can generate a massive response that blows the context window before max_round triggers. Always pair max_round with max_token_limit or a custom terminator that checks token usage.

“All agents need the same LLM config”

Each agent has its own llm_config. The manager has a separate one for selection. Use cheaper models for selection, stronger models for coding agents, and local models for summarization.

selection_config = {"model": "gpt-3.5-turbo", "temperature": 0}
coder_config = {"model": "gpt-4", "temperature": 0.2}
summarizer_config = {"model": "local-llama-3", "temperature": 0}

manager = GroupChatManager(groupchat=groupchat, llm_config=selection_config)
coder = AssistantAgent(name="coder", llm_config=coder_config, ...)
summarizer = AssistantAgent(name="summarizer", llm_config=summarizer_config, ...)

“Messages are immutable once sent”

You can modify groupchat.messages directly. This is useful for injecting corrections, removing sensitive data, or inserting summaries. Just maintain the message format: {"role": "assistant|user|system", "content": "...", "name": "agent_name"}.

Production configuration pattern

Here’s a battle-tested configuration for a code-review pipeline:

from autogen import GroupChat, GroupChatManager, AssistantAgent, UserProxyAgent
import tiktoken

# Models
SELECTION_MODEL = "gpt-3.5-turbo"
CODER_MODEL = "gpt-4"
REVIEWER_MODEL = "gpt-4"
SUMMARIZER_MODEL = "gpt-3.5-turbo"

selection_config = {"model": SELECTION_MODEL, "temperature": 0}
coder_config = {"model": CODER_MODEL, "temperature": 0.2}
reviewer_config = {"model": REVIEWER_MODEL, "temperature": 0.1}
summarizer_config = {"model": SUMMARIZER_MODEL, "temperature": 0}

# Agents
planner = AssistantAgent(
    name="planner",
    system_message="Create a step-by-step plan. Output as numbered list.",
    llm_config=coder_config,
    description="Plans the implementation approach."
)

coder = AssistantAgent(
    name="coder",
    system_message="Implement the plan. Write complete, runnable code. No explanations.",
    llm_config=coder_config,
    description="Writes production code per the plan."
)

reviewer = AssistantAgent(
    name="reviewer",
    system_message="Review code for correctness, security, and style. Output 'APPROVED' or 'ISSUES: <list>'.",
    llm_config=reviewer_config,
    description="Reviews code quality and correctness."
)

tester = AssistantAgent(
    name="tester",
    system_message="Write pytest tests for the code. Run them. Report pass/fail.",
    llm_config=coder_config,
    description="Writes and executes tests."
)

summarizer = AssistantAgent(
    name="summarizer",
    system_message="Summarize in 150 words: decisions, current state, blockers.",
    llm_config=summarizer_config
)

user_proxy = UserProxyAgent(
    name="user",
    human_input_mode="TERMINATE",
    max_consecutive_auto_reply=2,
    default_auto_reply="Continue. Say TERMINATE when done.",
    code_execution_config={"work_dir": "workspace", "use_docker": True}
)

agents = [planner, coder, reviewer, tester, user_proxy]

# Token counting
enc = tiktoken.encoding_for_model(CODER_MODEL)
def count_tokens(messages):
    return sum(len(enc.encode(m.get("content", ""))) for m in messages)

# Custom selector with explicit flow
def select_speaker(last_speaker, agents):
    name = last_speaker.name if last_speaker else None
    flow = {
        "user": "planner",
        "planner": "coder",
        "coder": "reviewer",
        "reviewer": lambda msgs: "coder" if "ISSUES:" in msgs[-1]["content"] else "tester",
        "tester": "user"
    }
    next_name = flow.get(name, "planner")
    if callable(next_name):
        next_name = next_name(groupchat.messages)
    return next(a for a in agents if a.name == next_name)

# Termination with multiple conditions
def should_stop(messages):
    if not messages:
        return False
    last = messages[-1]
    content = last.get("content", "")
    name = last.get("name", "")
    
    if name == "reviewer" and "APPROVED" in content:
        return True
    if "TERMINATE" in content:
        return True
    if count_tokens(messages) > 12000:  # leave room for response
        return True
    # Detect coder-reviewer ping-pong
    recent = [m.get("name") for m in messages[-6:]]
    if recent.count("coder") >= 3 and recent.count("reviewer") >= 3:
        return True
    return False

groupchat = GroupChat(
    agents=agents,
    messages=[],
    max_round=25,
    termination_condition=should_stop
)

manager = GroupChatManager(
    groupchat=groupchat,
    llm_config=selection_config,
    speaker_selection_method=select_speaker
)

# Periodic summarization
original_step = manager._process_received_message
def step_with_summary(message, sender):
    if len(groupchat.messages) > 10 and len(groupchat.messages) % 5 == 0:
        summary_msg = {"role": "user", "content": "Summarize conversation so far."}
        summary = summarizer.generate_reply(messages=[summary_msg])
        groupchat.messages.insert(-5, {"role": "system", "content": f"SUMMARY: {summary}"})
    return original_step(message, sender)
manager._process_received_message = step_with_summary

# Run
user_proxy.initiate_chat(manager, message="Build a rate limiter with Redis backend")

This configuration:

  • Uses deterministic routing (no LLM selection latency)
  • Enforces token limits at multiple levels
  • Detects and stops review loops
  • Injects summaries to maintain context
  • Runs code in Docker for isolation
  • Escalates to human only on termination

Debugging tips

When the chat behaves unexpectedly:

  1. Log every message — wrap groupchat.messages append to print speaker, token count, and truncation.
  2. Inspect selection prompts — set temperature=0 and log the prompt sent to the selection LLM.
  3. Check termination ordermax_round is checked first, then max_token_limit, then custom function. Know which fires.
  4. Verify agent descriptions — auto selection uses these verbatim. Vague descriptions cause wrong routing.
  5. Test selector in isolation — call your custom function with mock history before wiring it up.

When to use something else

GroupChatManager works for collaborative workflows with 3-6 agents. Beyond that, consider:

  • Sequential chains — LangGraph or custom orchestration for linear pipelines
  • Hierarchical teams — multiple group chats with a supervisor agent
  • Event-driven architectures — message queues for async, long-running tasks

The manager is a conversation primitive, not a workflow engine. Use it where the conversation is the work product — code review, design discussion, collaborative writing. For deterministic pipelines, write the flow explicitly.

Tagsautogengroupchatmanagerconfigurationgroup-chat

Written by

n4n Team

The team building n4n — a single OpenAI-compatible API in front of 240+ models, with automatic fallback, load balancing and pay-per-token metering.

More from n4n Team →

All autogen multi-agent conversations & group chat posts →