The problem: «AutoGen» now means at least three different things
You look for an AutoGen tutorial, copy from autogen import AssistantAgent, install a package, and the example does not run, or it runs but with a different API from the one in the text. It is not bad luck:
the name covers a 2023 research paper, a community-maintained framework (AG2) and a Microsoft line that is now archived, and the package you install decides which API you get. This article starts there,
because it is what costs you the most time, and then explains the conversational model with listings run locally, without models, using agents that follow a script.
The state of the packages on 2026-11-12, read from the PyPI registry and the official pages, is in the table.
| PyPI package | Version | What it is |
|---|---|---|
autogen | 0.14.1 | The «classic» framework (import autogen, ConversableAgent, GroupChat), maintained by AG2 |
ag2 | 1.3.1 | AG2 from version 1.0: a new framework, imported as ag2; it no longer provides the name autogen |
autogen-agentchat | 0.7.5 | The Microsoft line (AgentChat); the stable documentation declares the project archived |
pyautogen | 0.10.0 | A «proxy» package for autogen-agentchat, according to its description on the registry |
agent-framework | 1.26.0 | Microsoft Agent Framework, the successor named by Microsoft |
The point of the table is that the examples in this article (ConversableAgent, GroupChat) belong to the autogen row. The README of the ag2ai/ag2 repository says those classes
now live in «AG2 Classic» and recommends pip install ag2-classic, but on 2026-11-12 that name does not exist on PyPI (a 404 response from the registry's JSON API); the README of the ag2ai/ag2-classic repository instead gives
pip install autogen[openai]. So we installed autogen 0.14.1 in a clean virtual environment with Python 3.14.4 and ran every listing there. If you read the README and try ag2-classic, the installation fails: it is a documentation discrepancy, not a mistake of yours.
The conversational model
The paper that introduced the project (Wu and coauthors, arXiv:2308.08155, August 2023) describes it as an open-source framework for building applications on language models through several agents that converse to complete tasks; the agents are customizable and «conversable», and work in ways that combine models, human input and tools. The underlying idea is that conversation is the coordination primitive: instead of a graph of steps (as in LangGraph) or a sequence of tasks assigned to roles (as in CrewAI), the agents exchange messages and each one replies based on the history. The advantage is flexibility: you do not write all the paths in advance. The cost is the mirror image: you do not know in advance how many turns will be needed, or how it will end.
The block uses two ConversableAgent objects with llm_config=False, that is, without a model, and gives each one a reply function registered with register_reply that follows a script. It is the cheapest way to see
the mechanics: who speaks, when it stops, what ends up in the history. The Reviewer starts the conversation, the Analyst gives a wrong draft, the Reviewer disputes it, the Analyst corrects it and writes TERMINATE;
is_termination_msg recognizes that word and closes the round, and max_turns is a safety cap.
from autogen import ConversableAgent
def script(lines):
"""Reply function that follows a script: no model, no network."""
it = iter(lines)
def _reply(recipient, messages=None, sender=None, config=None):
return True, next(it) # (True = final reply, text)
return _reply
def done(msg):
return "TERMINATE" in (msg.get("content") or "")
reviewer = ConversableAgent("Reviewer", llm_config=False, human_input_mode="NEVER",
is_termination_msg=done)
analyst = ConversableAgent("Analyst", llm_config=False, human_input_mode="NEVER",
is_termination_msg=done)
reviewer.register_reply([ConversableAgent, None], script(["The number is off, check again."]))
analyst.register_reply([ConversableAgent, None],
script(["Draft: 3 orders are late.", "Corrected: 2 orders are late. TERMINATE"]))
outcome = reviewer.initiate_chat(analyst, message="How many orders are late?",
max_turns=6, silent=True)
for m in outcome.chat_history:
print(f'{m["name"]:9}: {m["content"]}')
print("messages:", len(outcome.chat_history))
# Output:
# Reviewer : How many orders are late?
# Analyst : Draft: 3 orders are late.
# Reviewer : The number is off, check again.
# Analyst : Corrected: 2 orders are late. TERMINATE
# messages: 4
Run twice with autogen 0.14.1, with identical output. The history holds four messages, in the order they were exchanged, and it is also the shared memory: every agent sees what has been said. If you remove
is_termination_msg, the conversation does not stop on the keyword and continues until the limits (here max_turns, and running out of script would raise StopIteration). With a real model the keyword
is a weak pact: it depends on the model actually writing it, which is why you always need a numeric cap as well.
What the value is if you do not write the parameter
An agent's behavior depends on a few parameters, and tutorials stay silent about the defaults. The block reads them from the signature of ConversableAgent and GroupChat in the installed version, and tries an invented
value of human_input_mode to see whether it is rejected.
import inspect
from autogen import ConversableAgent, GroupChat
# What the parameter is worth if you don't write it (read from the signature, autogen 0.14.1)
sig = inspect.signature(ConversableAgent.__init__).parameters
for name in ("human_input_mode", "max_consecutive_auto_reply", "code_execution_config", "llm_config"):
print(f"{name:28} = {sig[name].default!r}")
print("MAX_CONSECUTIVE_AUTO_REPLY =", ConversableAgent.MAX_CONSECUTIVE_AUTO_REPLY)
sig = inspect.signature(GroupChat).parameters
for name in ("max_round", "speaker_selection_method"):
print(f"GroupChat.{name:19} = {sig[name].default!r}")
# An invented value is NOT rejected at creation
a = ConversableAgent("x", llm_config=False, human_input_mode="FUNCTION_CALL")
print("invented human_input_mode accepted:", a.human_input_mode)
# Output:
# human_input_mode = 'TERMINATE'
# max_consecutive_auto_reply = None
# code_execution_config = False
# llm_config = None
# MAX_CONSECUTIVE_AUTO_REPLY = 100
# GroupChat.max_round = 10
# GroupChat.speaker_selection_method = 'auto'
# invented human_input_mode accepted: FUNCTION_CALL
Three things show in the output. human_input_mode is 'TERMINATE': an agent that does not specify it stops to ask for human input when it receives a termination message or reaches the limit of automatic replies.
In a non-interactive script that means staying blocked waiting for the keyboard; that is why the listings use "NEVER". The max_consecutive_auto_reply limit is None and the class cap is 100: an agent that does not set it can reply up to a hundred times in a row.
Finally, the invented value FUNCTION_CALL is accepted without an error: the docstring lists only three modes, ALWAYS (asks at every message), TERMINATE and NEVER. A tutorial that talks about a fourth mode, or a typo in the name, does not produce
an error but a behavior that is not the one you think.
Groups of agents: GroupChat and who speaks
With more than two agents someone has to decide who speaks. GroupChat defines the participants and the rules, GroupChatManager runs the conversation. The speaker_selection_method parameter
accepts, in the installed version, four values:
"auto"(the default): a model reads the conversation and chooses who speaks, which adds at least one model call at every turn (not measured here)."round_robin": they speak in turn, in order; no selection call, predictable behavior."random": picks at random among the participants."manual": asks a person who should speak.
The list has an order of cost and control: auto is the most flexible and the most expensive, manual the most controlled, round_robin the simplest to test. To try a group without a model,
the block uses round_robin and three scripted agents; the manager has a termination condition on the word TERMINATE and max_round set to 6 as a cap. The manager prints a report: we silence it with
redirect_stdout to show only the message history.
import contextlib
import io
from autogen import ConversableAgent, GroupChat, GroupChatManager
def script(lines):
it = iter(lines)
def _reply(recipient, messages=None, sender=None, config=None):
return True, next(it)
return _reply
def agent(name, lines):
a = ConversableAgent(name, llm_config=False, human_input_mode="NEVER")
a.register_reply([ConversableAgent, None], script(lines))
return a
author = agent("Author", ["Function written: sort_list(items).", "Added the empty-list case."])
reviewer = agent("Reviewer", ["The empty-list case is missing.", "Looks good now. TERMINATE"])
tester = agent("Tester", ["Tests written: 3 cases."])
user = ConversableAgent("User", llm_config=False, human_input_mode="NEVER")
group = GroupChat(agents=[author, reviewer, tester], messages=[], max_round=6,
speaker_selection_method="round_robin") # strict turns, no model picks
manager = GroupChatManager(groupchat=group, llm_config=False,
is_termination_msg=lambda m: "TERMINATE" in (m.get("content") or ""))
with contextlib.redirect_stdout(io.StringIO()): # the manager prints a report: we silence it
user.initiate_chat(manager, message="Write a function that sorts a list.", silent=True)
for m in group.messages:
print(f'{m["name"]:12}: {m["content"]}')
# Output:
# User : Write a function that sorts a list.
# Author : Function written: sort_list(items).
# Reviewer : The empty-list case is missing.
# Tester : Tests written: 3 cases.
# Author : Added the empty-list case.
# Reviewer : Looks good now. TERMINATE
Run twice, with identical output. The order Author, Reviewer, Tester, Author, Reviewer is that of round_robin, and the conversation stops at the sixth message because the Reviewer writes TERMINATE. With auto the order would depend on a model and could change
on every run: we did not run it. Note that the Tester speaks only once before the end: in a real group an agent can end up never speaking, or speaking too much, and it has to be checked.
The write-run-fix loop
The pattern AutoGen is best known for makes two agents work together: one writes code, the other runs it and returns the result, errors included; whoever wrote it reads the traceback and tries again. In the block the assistant is scripted
(a first attempt that divides by zero, a second one that is correct) and the executor is a ConversableAgent with a code_execution_config that uses LocalCommandLineCodeExecutor in a temporary folder, which disappears at the end.
The code really is executed, in a local subprocess: it is the only listing in the article that does so.
import contextlib
import io
import tempfile
from autogen import ConversableAgent
from autogen.coding import LocalCommandLineCodeExecutor
def script(lines):
it = iter(lines)
def _reply(recipient, messages=None, sender=None, config=None):
return True, next(it)
return _reply
FENCE = "`" * 3
attempt_1 = f"{FENCE}python\nprint(sum([1, 2, 3]) / len([]))\n{FENCE}" # divides by zero
attempt_2 = f"{FENCE}python\nvalues = [1, 2, 3]\nprint(sum(values) / len(values))\n{FENCE}"
assistant = ConversableAgent("Assistant", llm_config=False, human_input_mode="NEVER",
is_termination_msg=lambda m: "TERMINATE" in (m.get("content") or ""))
assistant.register_reply([ConversableAgent, None],
script([attempt_1, attempt_2, "The mean is 2.0. TERMINATE"]))
with tempfile.TemporaryDirectory() as folder: # the code runs here, then the folder disappears
executor = LocalCommandLineCodeExecutor(work_dir=folder, timeout=10)
proxy = ConversableAgent("Executor", llm_config=False, human_input_mode="NEVER",
code_execution_config={"executor": executor},
is_termination_msg=lambda m: "TERMINATE" in (m.get("content") or ""))
with contextlib.redirect_stdout(io.StringIO()):
proxy.initiate_chat(assistant, message="Compute the mean of 1, 2, 3.", max_turns=8, silent=True)
messages = proxy.chat_messages[assistant]
for m in messages:
lines = [r for r in m["content"].splitlines() if r.strip() and "`" * 3 not in r]
if m["name"] == "Executor" and lines[0].startswith("exitcode"):
lines = [lines[0], lines[-1]] # outcome and last line (the traceback contains local paths)
print(f'{m["name"]:10}:', " / ".join(lines))
# Output:
# Executor : Compute the mean of 1, 2, 3.
# Assistant : print(sum([1, 2, 3]) / len([]))
# Executor : exitcode: 1 (execution failed) / ZeroDivisionError: division by zero
# Assistant : values = [1, 2, 3] / print(sum(values) / len(values))
# Executor : exitcode: 0 (execution succeeded) / Code output: 2.0
# Assistant : The mean is 2.0. TERMINATE
Run twice with identical output. The first attempt ends with exitcode: 1 and ZeroDivisionError; the second returns 2.0 with exitcode: 0, and the assistant closes. Two measured warnings. The first: the executor calls the python command; on a machine that has only
python3 in the PATH the execution fails with FileNotFoundError: [Errno 2] No such file or directory: 'python' (tried with PATH=/usr/bin:/bin), and we ran with the virtual environment activated. The second concerns security:
LocalCommandLineCodeExecutor runs on your machine, with your permissions, code written by a model. Calling it a «safe environment» would be wrong; for any use other than an experiment you need isolation (a container, a dedicated machine, minimal permissions), as discussed in
Security and Safety. Docker was not used in this article.
What «emergent» means, and where it hurts
The title speaks of emergent conversation: the flow arises from the messages, it is not written. It is an advantage when the task is open-ended (a review that needs several passes), but it has three costs you can predict from how it works. First, the number of turns is not known: you always need a cap
(max_turns, max_round, max_consecutive_auto_reply) and a stopping condition. Second, every agent sees the whole history, so every call to a model carries a context that grows at every turn: the cost grows faster than the number of turns (not measured here, because we did not call models).
Third, reproducibility is low: two runs can give different conversations, which makes testing hard; that is why the tests in this article use scripts, and a real agent is evaluated on many cases, as in
Testing and Evaluation.
Migrating, and choosing today
The stable AutoGen documentation warns that the project is archived and no longer maintained by Microsoft (no bug or security fixes), advises against production use and points new users to Microsoft Agent Framework, with a migration guide. The Microsoft Learn page describes Agent Framework as the combination of AutoGen's simple abstractions with the enterprise features of Semantic Kernel (session state, types, middleware, telemetry), plus graph-based workflows for explicit orchestration; the same page reminds you, usefully for anyone about to build an agent, that if you can write a function that solves the task, you should use the function. On the AG2 side, the README says that version 1.0 is not a compatible upgrade of the classic one (agent model, orchestration and imports have changed) and points to a migration guide for groups.
| Your situation | What makes sense |
|---|---|
You have working code with import autogen | Pin the version of autogen (0.14.1 on 2026-11-12) and do not install ag2 1.x thinking it is an upgrade |
You have code on AutoGen 0.4 / autogen-agentchat | Plan the migration: the project is archived, the Microsoft guide leads to Agent Framework |
| You start from scratch and the flow is known | Consider a workflow or a graph first, as in the previous articles: free conversation has a price |
| You start from scratch and want conversation between agents | Evaluate Agent Framework or AG2 1.x, reading their current documentation; we did not run them here |
The table does not say «use X»: it says to read first which row applies to you, because the rows have different migration costs. We did not install or run agent-framework or ag2 1.x, so we cannot say how the same example is written today with them; we only know, from the sources, that the API has changed.
Coordinating several agents in production is the topic of the next article, Multi-Agent Orchestration: Production Architectures, and the overview of the frameworks is in the
first article.
Frequently asked questions
Is AutoGen still maintained?
It depends on which one: Microsoft's stable documentation declares the project archived and no longer maintained; the classic framework continues in the AG2 repository (package autogen, 0.14.1 on 2026-11-12). For new code Microsoft points to Agent Framework.
Why does pip install ag2-classic not work?
pip install ag2-classic not work?On 2026-11-12 the ag2-classic package does not exist on PyPI, even though one README mentions it; the other README gives autogen[openai]. We installed autogen 0.14.1 and the listings ran.
How do I stop a conversation between agents from never ending?
With several limits together: max_turns or max_round, max_consecutive_auto_reply and a termination condition with is_termination_msg. The default of max_consecutive_auto_reply is no limit per instance and a class cap of 100, too high for an agent that calls a paid model.
What you can do now
You can tell which AutoGen a tutorial was written for from the package and the import, and you can install the right one. You can make two or more agents converse, stop them with a condition and a cap, choose who speaks in a group, and have code written, run and corrected, knowing where local execution is dangerous and where it breaks. You can also test everything with scripted agents before connecting a model. And you know that today's decision is about the future of the project, not just the syntax.
Sources
- AutoGen: Enabling Next-Gen LLM Applications via Multi-Agent Conversation (Wu et al., arXiv:2308.08155) - original description of the conversational framework; abstract read on 2026-11-12
- AutoGen - stable documentation (Microsoft) - archived-project notice and pointer to Agent Framework; consulted on 2026-11-12
- Repository ag2ai/ag2, README - AG2 1.0, the
ag2name, AG2 Classic, non-compatibility with the classic one; consulted on 2026-11-12 - Repository ag2ai/ag2-classic, README - installing the classic framework with
autogen; consulted on 2026-11-12 - Microsoft Agent Framework - Overview (Microsoft Learn) - description of the successor and the advice to prefer a function when it is enough; consulted on 2026-11-12
- AutoGen to Microsoft Agent Framework Migration Guide (Microsoft Learn) - existence of the migration guide; consulted on 2026-11-12
- autogen on PyPI - versions and descriptions of the packages in the table, read with the registry's JSON API on 2026-11-12. The default values and the behavior of the listings are observed by running them, not read from the documentation

Commenti
Caricamento commenti...
Sign in to comment