5 AutoGen Agent Roles (Assistant, UserProxy, CodeExecutor)
Understand the 5 core AutoGen agent types — AssistantAgent, UserProxyAgent, CodeExecutorAgent, and more — with code examples and a comparison table for each role.
Get more content like this on Telegram!
Daily AI tips, notes & resources — free
When you first look at AutoGen's agent types, the naming can mislead you. "UserProxy" sounds like it represents a human user. "AssistantAgent" sounds like it answers questions. Both descriptions are partially true but miss the deeper design logic behind each role.
AutoGen's agent types are less about personality and more about responsibility — who holds the LLM, who runs code, who decides when to ask a human. Getting these responsibilities right is the difference between a multi-agent system that produces good results and one that argues with itself in circles.
This guide covers the five agent types you will actually use, with code for each and a clear explanation of when each one fits.
For broader context on agent architectures before the AutoGen specifics, AI agents explained and AI agent memory and planning set up the mental model well.
The Base: ConversableAgent
Every AutoGen agent inherits from ConversableAgent. Understanding its key parameters applies to all types:
import autogen
# ConversableAgent — the foundation
base_agent = autogen.ConversableAgent(
name="BaseAgent",
# LLM configuration — set to False for non-LLM agents
llm_config={
"config_list": [{"model": "gpt-4o", "api_key": "YOUR_KEY"}],
"temperature": 0.3,
"cache_seed": None,
},
# Code execution config — set to False if agent doesn't execute code
code_execution_config={
"work_dir": "workspace",
"use_docker": False,
"timeout": 60,
},
# Human input mode: ALWAYS, NEVER, or TERMINATE
human_input_mode="NEVER",
# Stop condition — check message content for termination keyword
is_termination_msg=lambda msg: "DONE" in msg.get("content", ""),
# Max auto-replies before stopping
max_consecutive_auto_reply=10,
# System message — defines agent behavior
system_message="You are a helpful agent.",
)
All five agent types share these parameters. The differences are in defaults and intended use.
Agent Type 1: AssistantAgent
AssistantAgent is the LLM-powered reasoning engine. It generates responses, plans tasks, writes code, and produces text outputs. It does not execute code by default.
config_list = [{"model": "gpt-4o", "api_key": "YOUR_KEY"}]
llm_config = {"config_list": config_list, "temperature": 0.2}
assistant = autogen.AssistantAgent(
name="Assistant",
llm_config=llm_config,
system_message="""You are an expert Python developer.
When given coding tasks:
1. Write clean, well-commented Python code
2. Include error handling for edge cases
3. Explain your approach briefly before the code block
4. Say TASK_COMPLETE when the solution is finished and tested""",
is_termination_msg=lambda msg: "TASK_COMPLETE" in msg.get("content", ""),
)
Defaults:
llm_config: required (no LLM = no reasoning)code_execution_config:False— assistant writes code but does not run ithuman_input_mode:NEVER— does not ask for human input
When to use it: Any role that requires reasoning, generation, or planning. Researchers, writers, planners, coders — all of these are AssistantAgent variants with different system messages.
Customizing system messages:
# Research-focused assistant
researcher = autogen.AssistantAgent(
name="Researcher",
llm_config=llm_config,
system_message="""You are a research specialist.
When researching, always cite your sources and flag uncertain information.
Format: use **bold** for key findings, mark speculation with [UNCERTAIN].""",
)
# Code review assistant
code_reviewer = autogen.AssistantAgent(
name="CodeReviewer",
llm_config=llm_config,
system_message="""You review Python code for:
- Correctness: does it do what it claims?
- Security: are there injection or path traversal risks?
- Performance: are there obvious inefficiencies?
Rate each dimension 1-5 and provide specific fix suggestions.""",
)
Agent Type 2: UserProxyAgent
UserProxyAgent is the execution and human-interface layer. Despite the name, it does not have to represent a human at all — in fully automated pipelines, it runs with human_input_mode="NEVER" and acts as the code execution and conversation-triggering component.
user_proxy = autogen.UserProxyAgent(
name="UserProxy",
human_input_mode="NEVER", # no human in the loop
code_execution_config={
"work_dir": "workspace",
"use_docker": False,
"timeout": 60,
"last_n_messages": 3, # look back 3 messages for code to execute
},
max_consecutive_auto_reply=8,
is_termination_msg=lambda msg: "TASK_COMPLETE" in msg.get("content", ""),
)
The three human_input_mode options:
# Mode 1: NEVER — fully automated, no human intervention
user_proxy = autogen.UserProxyAgent(
name="AutoProxy",
human_input_mode="NEVER",
code_execution_config={"work_dir": "workspace", "use_docker": False},
)
# Mode 2: TERMINATE — ask human only when agent says TERMINATE
user_proxy = autogen.UserProxyAgent(
name="HumanProxy",
human_input_mode="TERMINATE",
code_execution_config={"work_dir": "workspace", "use_docker": False},
)
# Mode 3: ALWAYS — human approves every message (useful for development)
user_proxy = autogen.UserProxyAgent(
name="SupervisedProxy",
human_input_mode="ALWAYS",
code_execution_config={"work_dir": "workspace", "use_docker": False},
)
When to use ALWAYS: During development when you want to inspect and optionally modify each agent response before it is sent. Slows things down significantly but gives complete visibility.
When to use TERMINATE: Production workflows where you want automation but need a human checkpoint before the final deliverable is accepted or acted on.
Agent Type 3: CodeExecutorAgent
CodeExecutorAgent is new in AutoGen 0.4+ and provides a cleaner API for execution-focused agents. It separates the executor configuration from agent configuration more explicitly.
from autogen import CodeExecutorAgent
from autogen.coding import LocalCommandLineCodeExecutor, DockerCommandLineCodeExecutor
from pathlib import Path
# Local execution
local_executor = LocalCommandLineCodeExecutor(
timeout=60,
work_dir=Path("coding_workspace"),
)
code_executor = CodeExecutorAgent(
name="CodeExecutor",
code_executor=local_executor,
)
# Docker execution (for production)
docker_executor = DockerCommandLineCodeExecutor(
image="python:3.11-slim",
timeout=120,
work_dir=Path("docker_workspace"),
auto_remove=True,
)
secure_executor = CodeExecutorAgent(
name="SecureExecutor",
code_executor=docker_executor,
)
CodeExecutorAgent vs UserProxyAgent with code execution:
| Aspect | CodeExecutorAgent | UserProxyAgent |
|---|---|---|
| API version | AutoGen 0.4+ | All versions |
| Executor setup | Separate class | Config dict |
| Docker support | First-class | Via config |
| LLM capability | None by default | Optional |
| Human input | No | Yes |
| Preferred for | Pure execution | Execution + human-in-loop |
For a full deep-dive on code execution patterns, AutoGen code interpreter guide covers LocalCommandLineCodeExecutor and DockerCommandLineCodeExecutor in detail.
Agent Type 4: Custom Agents (ConversableAgent Subclasses)
The fourth category is custom agents — any agent built by subclassing ConversableAgent (or one of its subclasses) to add specialized behavior.
class DataValidatorAgent(autogen.ConversableAgent):
"""An agent that validates data before passing it forward."""
def __init__(self, validation_schema, **kwargs):
super().__init__(**kwargs)
self.validation_schema = validation_schema
self.validation_results = []
def generate_reply(self, messages=None, sender=None, **kwargs):
"""Override to add pre-processing validation."""
if messages:
last_message = messages[-1].get("content", "")
# Custom validation logic
validation_result = self._validate(last_message)
if not validation_result["valid"]:
# Return validation error instead of normal reply
return f"Validation failed: {validation_result['errors']}. Please correct and resubmit."
# Proceed with normal LLM reply
return super().generate_reply(messages=messages, sender=sender, **kwargs)
def _validate(self, content):
"""Validate content against schema."""
errors = []
for rule in self.validation_schema:
if rule["type"] == "contains" and rule["value"] not in content:
errors.append(f"Missing required element: {rule['value']}")
if rule["type"] == "min_length" and len(content) < rule["value"]:
errors.append(f"Content too short: minimum {rule['value']} characters")
return {"valid": len(errors) == 0, "errors": errors}
# Create a validator that ensures reports have required sections
report_validator = DataValidatorAgent(
validation_schema=[
{"type": "contains", "value": "## Summary"},
{"type": "contains", "value": "## Recommendations"},
{"type": "min_length", "value": 300},
],
name="ReportValidator",
llm_config=llm_config,
system_message="You validate and improve reports to meet quality standards.",
human_input_mode="NEVER",
)
More custom agent patterns:
class MemoryAgent(autogen.AssistantAgent):
"""Agent that persists memory across conversation sessions."""
def __init__(self, memory_file="agent_memory.json", **kwargs):
super().__init__(**kwargs)
self.memory_file = memory_file
self.long_term_memory = self._load_memory()
def _load_memory(self):
try:
with open(self.memory_file, "r") as f:
import json
return json.load(f)
except FileNotFoundError:
return {}
def _save_memory(self, key, value):
self.long_term_memory[key] = value
with open(self.memory_file, "w") as f:
import json
json.dump(self.long_term_memory, f, indent=2)
def generate_reply(self, messages=None, sender=None, **kwargs):
# Inject memory context into the conversation
memory_context = f"\nRelevant memory: {self.long_term_memory}\n"
if messages:
# Add memory to the last message
enriched_messages = messages.copy()
if enriched_messages:
enriched_messages[-1] = {
**enriched_messages[-1],
"content": memory_context + enriched_messages[-1].get("content", "")
}
reply = super().generate_reply(messages=enriched_messages or messages, sender=sender, **kwargs)
# Save any new insights to memory
if reply and "REMEMBER:" in reply:
# Parse and save memory items
lines = reply.split("\n")
for line in lines:
if line.startswith("REMEMBER:"):
key_value = line.replace("REMEMBER:", "").strip()
if "=" in key_value:
k, v = key_value.split("=", 1)
self._save_memory(k.strip(), v.strip())
return reply
Agent Type 5: GroupChatManager
GroupChatManager is the fifth distinct agent type — the orchestrator that coordinates multi-agent conversations.
import autogen
config_list = [{"model": "gpt-4o", "api_key": "YOUR_KEY"}]
llm_config = {"config_list": config_list}
# Create team agents
planner = autogen.AssistantAgent(
name="Planner",
system_message="Create detailed task plans. Break down complex goals into steps.",
llm_config=llm_config,
)
executor = autogen.AssistantAgent(
name="Executor",
system_message="Execute tasks from the plan. Write and verify code. Report results.",
llm_config=llm_config,
)
critic = autogen.AssistantAgent(
name="Critic",
system_message="Review plans and results critically. Identify gaps and improvements.",
llm_config=llm_config,
)
user = autogen.UserProxyAgent(
name="User",
human_input_mode="NEVER",
code_execution_config={"work_dir": "workspace", "use_docker": False},
)
# GroupChat holds the agents and manages the conversation state
group_chat = autogen.GroupChat(
agents=[user, planner, executor, critic],
messages=[],
max_round=18,
speaker_selection_method="auto", # LLM decides who speaks next
speaker_transitions_type="allowed",
allowed_or_disallowed_speaker_transitions={
user: [planner],
planner: [executor, critic],
executor: [critic, planner],
critic: [planner, executor, user],
},
)
# GroupChatManager wraps the GroupChat and acts as the conversation router
manager = autogen.GroupChatManager(
groupchat=group_chat,
llm_config=llm_config,
system_message="You manage the conversation flow. Keep the team focused on the task.",
)
# The manager receives the initial message and routes it
user.initiate_chat(
manager,
message="Build a Python web scraper that collects the top 10 trending GitHub repositories daily.",
)
GroupChatManager parameters:
manager = autogen.GroupChatManager(
groupchat=group_chat,
llm_config=llm_config,
# How many messages to include when selecting next speaker
# More context = better decisions but higher cost
select_speaker_message_template="""You are managing a group chat.
Select the next speaker based on what the task needs.
Current participants: {roles}
Recent conversation: {messages}
Who should speak next?""",
# Trigger human input at the end
human_input_mode="TERMINATE",
)
For detailed patterns on using GroupChatManager with different speaker selection strategies, AutoGen group chat patterns covers round-robin, custom functions, and nested chat architectures.
Agent Role Comparison Table
| Agent Type | LLM | Code Execution | Human Input | Best For |
|---|---|---|---|---|
| AssistantAgent | Yes | No (default) | No | Reasoning, writing, planning |
| UserProxyAgent | Optional | Yes | Optional | Execution + human-in-loop |
| CodeExecutorAgent | No | Yes | No | Pure code execution |
| Custom (subclass) | Optional | Optional | Optional | Specialized workflows |
| GroupChatManager | Yes | No | Optional | Multi-agent orchestration |
Practical Two-Agent Minimum Pattern
Every AutoGen workflow needs at least two agents. The canonical minimum is:
import autogen
config_list = [{"model": "gpt-4o", "api_key": "YOUR_KEY"}]
llm_config = {"config_list": config_list}
# Agent 1: The thinker
assistant = autogen.AssistantAgent(
name="Assistant",
llm_config=llm_config,
system_message="You are a helpful coding assistant. Write working Python code.",
)
# Agent 2: The doer
user_proxy = autogen.UserProxyAgent(
name="User",
human_input_mode="NEVER",
code_execution_config={
"work_dir": "workspace",
"use_docker": False,
},
max_consecutive_auto_reply=5,
)
# One line to start the conversation
user_proxy.initiate_chat(
assistant,
message="Write a Python function that checks if a number is prime. Test it with 10 examples.",
)
This two-agent pattern handles a surprising range of tasks. The assistant writes code, the user proxy runs it, feeds back the output, and the assistant revises until correct.
When to Add More Agents
Add a third agent when you need a distinct role that would otherwise clutter one agent's system message:
- Researcher + Writer + Editor (three distinct content roles)
- Planner + Executor + Reviewer (three distinct process roles)
- Frontend Agent + Backend Agent + Database Agent (three distinct domain roles)
Beyond five or six agents, the cognitive load in the group chat increases substantially, and the quality of speaker selection (even with LLM-based auto selection) tends to degrade. If you need more than six agents, nested group chats with sub-teams typically produce better results.
For frameworks that take a different approach to agent roles, CrewAI tutorial shows how role definitions work in CrewAI's organizational model, and AutoGen vs CrewAI comparison puts both side by side on the same task.
The agent role system in AutoGen is flexible precisely because it is minimal. You get five types with clear responsibilities, and the customization surface is large enough to build anything you need.
Frequently Asked Questions
What is the difference between AssistantAgent and UserProxyAgent in AutoGen? AssistantAgent uses an LLM to generate responses — it reasons, plans, and produces outputs. UserProxyAgent represents the human or execution layer — it can execute code, ask for human input, and trigger other agents. Most workflows pair one AssistantAgent with one UserProxyAgent as the minimum working configuration.
Can a UserProxyAgent have an LLM in AutoGen? Yes. Setting llm_config on a UserProxyAgent enables it to use an LLM when human_input_mode is set to NEVER. This makes it behave like an AssistantAgent that also has code execution capabilities. However, in most architectures, keeping these roles separate is cleaner.
What is ConversableAgent and how does it relate to other agent types? ConversableAgent is the base class for all AutoGen agents. AssistantAgent, UserProxyAgent, and CodeExecutorAgent are all subclasses of ConversableAgent. You can subclass ConversableAgent directly to create fully custom agents that inherit all the conversation and tool-use infrastructure.
Frequently Asked Questions
AiTechWorlds Team
✓ Verified WriterThe AiTechWorlds team is passionate about AI, technology, and education. We create high-quality, research-backed content to help you learn, grow, and succeed in the modern digital world.
Related Articles
How to Deploy AutoGen Agents as APIs with FastAPI (2026)
Learn to serve AutoGen multi-agent systems as production REST APIs using FastAPI with async endpoints and real-time streaming responses.
How to Use AutoGen with Azure OpenAI (Enterprise Security)
Connect Microsoft AutoGen to Azure OpenAI for enterprise-grade AI agents. Step-by-step setup with private endpoints, OAI_CONFIG_LIST, and deployment config.
Build a Code Debugging Agent with AutoGen (Auto-Fix PRs)
Build an AutoGen agent that reviews code, analyzes PR diffs, suggests fixes, and automates code quality improvements with a full working implementation.
How to Use AutoGen with Code Interpreter (Execute Python)
Learn how to set up AutoGen's code interpreter with LocalCommandLineCodeExecutor and DockerCommandLineCodeExecutor to safely execute Python in agent workflows.