← Corpus / agent-harnesses / profile
autogen
- Path
- profiles/Profile__AutoGen.md
AutoGen — Profile
A profile of AutoGen as it lives in this study (studies/agent-harnesses/autogen/). Cites pinned paths so you can jump to source rather than trust paraphrase. Read alongside Profile__OpenHands.md (event-stream trace architecture) and any opencode profile in this study (CLI-first single-agent, own MCP client) — AutoGen is this study’s entry for native multi-agent team orchestration with an explicit, swappable turn-taking algorithm per team type, layered under a distributed actor-style runtime rather than built as a single-agent loop with delegation.
README.md:14,18-25 states the repo’s current status plainly: AutoGen is in maintenance mode — “It will not receive new features or enhancements and is community managed going forward,” with Microsoft steering new projects to Microsoft Agent Framework. This checkout is the 0.4+ generation (confirmed via python/packages/{autogen-core,autogen-agentchat,autogen-ext}/pyproject.toml, all version = "0.7.5"), the layered rewrite that replaced the 0.2.x monolithic pyautogen package (still present at python/packages/pyautogen/ for migration reference, not read for this profile).
TL;DR
The README’s own architecture summary (README.md:179-183):
The autogen framework uses a layered and extensible design… [Core API] implements message passing, event-driven agents, and local and distributed runtime… [AgentChat API] implements a simpler but opinionated API for rapid prototyping… [Extensions API] enables first- and third-party extensions.
Mechanically: teams are actor-runtime topics, not a shared Python loop. BaseGroupChat._init() (python/packages/autogen-agentchat/src/autogen_agentchat/teams/_group_chat/_base_group_chat.py:191-245) registers every participant as a ChatAgentContainer core-agent behind its own topic type (f"{participant.name}_{team_id}"), plus a group-chat-manager agent on a dedicated manager topic, plus a shared group topic all participants subscribe to for broadcast. Turn-taking isn’t a for-loop over a list — it’s GroupChatRequestPublish messages published to the runtime (SingleThreadedAgentRuntime by default, _base_group_chat.py:141) and consumed asynchronously.
Speaker selection is delegated to a select_speaker() abstract method (_base_group_chat_manager.py:305-318) implemented differently per team type: RoundRobinGroupChatManager.select_speaker() (_round_robin_group_chat.py:72-82) is a plain modular index increment; SelectorGroupChatManager.select_speaker() (_selector_group_chat.py:152-217) calls an LLM with a role-and-history prompt (_select_speaker(), _selector_group_chat.py:232-308), re-prompting on invalid/repeated/multi-name responses up to max_selector_attempts (default 3) before falling back to the previous speaker or first participant; SwarmGroupChatManager.select_speaker() (_swarm_group_chat.py:82-98) reads the target field off the most recent HandoffMessage in the thread — no model call at all, just a scan.
Conversation state persists as a nested, per-agent-name dictionary, not a single transcript. BaseGroupChat.save_state() (_base_group_chat.py:748-796) calls AgentRuntime.agent_save_state() on every participant plus the group-chat-manager and nests results under TeamState.agent_states keyed by agent name (not agent ID — _base_group_chat.py:783-786 explicitly decouples state from runtime identity so state is portable across teams/runtimes). Each AssistantAgent.save_state() (autogen-agentchat/src/autogen_agentchat/agents/_assistant_agent.py:1630-1633) in turn delegates to its ChatCompletionContext.save_state() (autogen-core/src/autogen_core/model_context/_chat_completion_context.py:66-67), which just dumps the raw LLMMessage list. Group-chat-manager state additionally carries algorithm-specific fields — RoundRobinManagerState.next_speaker_index, SelectorManagerState.previous_speaker, SwarmManagerState.current_speaker (all in autogen-agentchat/src/autogen_agentchat/state/_states.py:43-61) — so resuming a team restores not just messages but exactly where the turn-taking algorithm left off.
Tool-calling is native-first, MCP-second, and the direction of the bridge matters. autogen_core.tools.Tool (autogen-core/src/autogen_core/tools/_base.py:56-80) is a Protocol with run_json/save_state_json/load_state_json; BaseTool (_base.py:96-215) and FunctionTool (autogen-core/src/autogen_core/tools/_function_tool.py:30) are the native implementations, predating this checkout’s MCP support. MCP is bridged into this native shape, not the reverse: McpToolAdapter(BaseTool[BaseModel, Any], ABC, ...) (autogen-ext/src/autogen_ext/tools/mcp/_factory.py:29) wraps a single mcp.Tool so it satisfies the native Tool protocol, and Workbench (autogen-core/src/autogen_core/tools/_workbench.py:78-93) — also native-core, predating MCP — gets its own MCP-backed implementation, McpWorkbench (autogen-ext/src/autogen_ext/tools/mcp/_workbench.py:47). The mcp package is an optional extra (autogen-ext/pyproject.toml:153, mcp = ["mcp>=1.11.0"]), not a core dependency.
If you want one sentence: AutoGen runs multi-agent “teams” as actor-runtime topic graphs where a pluggable select_speaker() method — round-robin index, LLM-prompted role selection, or handoff-message target-reading — decides the next turn, conversation state saves as a name-keyed nested dict of each participant’s serialized ChatCompletionContext plus manager-specific turn-tracking fields, and tool-calling is fundamentally the native Tool/BaseTool/Workbench protocol with MCP wired in as an adapter layer (McpToolAdapter, McpWorkbench) behind an optional [mcp] extra, not the other way around.
Why this exists — three load-bearing design choices
- The group chat is a runtime topology, not a loop.
BaseGroupChat._init()(_base_group_chat.py:191-245) wires aTypeSubscriptionper participant (own topic + shared group topic) and one for the manager (own topic + group topic + output topic). This is whyTeamcomposes: aRoundRobinGroupChatcan nest anotherRoundRobinGroupChatas one of its own participants (demonstrated in the docstring example,_round_robin_group_chat.py:184-233) — the inner team is just anotherChatAgentContainer-wrapped agent from the outer team’s perspective (_chat_agent_container.py:79-81checksisinstance(self._agent, Team)and callsrun_streamon it instead ofon_messages_stream). select_speaker()is the one seam every team type overrides — everything else inBaseGroupChatManageris shared._transition_to_next_speakers()(_base_group_chat_manager.py:172-193) is identical across Round Robin / Selector / Swarm; it awaitsself.select_speaker(self._message_thread)polymorphically and publishesGroupChatRequestPublishto whichever topic(s) come back. Termination checking (_apply_termination_condition,_base_group_chat_manager.py:195-228), message-thread bookkeeping, and turn counting are all base-class, non-overridden logic.- State save/load is decoupled from runtime identity on purpose. The docstring on
save_state(_base_group_chat.py:766-771) states this was a breaking change as of v0.4.9 specifically so saved state is “portable across different teams and runtimes” — moving from agent-ID-keyed to agent-name-keyed state.load_state(_base_group_chat.py:798-830) catchespydantic.ValidationErrorand raises a pointed error telling the caller the format changed and to read the release notes (_base_group_chat.py:826-830) — an explicit acknowledgment that old saved state will not silently load wrong.
Turn-taking algorithms, compared at the source
All three managers subclass BaseGroupChatManager (_base_group_chat_manager.py:25) and only differ in select_speaker() + reset() + save_state()/load_state():
- RoundRobinGroupChatManager (
_round_robin_group_chat.py:16-82):self._next_speaker_index = (current_speaker_index + 1) % len(self._participant_names)(_round_robin_group_chat.py:79-82) — pure modular arithmetic, no model call, always returns exactly one speaker. - SelectorGroupChatManager (
_selector_group_chat.py:50-341): builds arolesblock (name + description per participant,_selector_group_chat.py:203-208), formats theselector_prompttemplate with{roles}/{participants}/{history}, and callsself._model_client.create(...)(or.create_streamifmodel_client_streaming)._mentioned_agents()(_selector_group_chat.py:310-341) regex-matches the model’s free-text response against participant names (handling underscore-vs-space and escaped-underscore variants), retries up tomax_selector_attemptson zero or multiple matches, and can be overridden entirely by a user-suppliedselector_func(sync or async, checked first,_selector_group_chat.py:163-177) or narrowed by acandidate_functhat filters the participant pool before the LLM call (_selector_group_chat.py:180-193).allow_repeated_speaker(defaultFalse) excludes the previous speaker from candidates unless explicitly permitted. - SwarmGroupChatManager (
_swarm_group_chat.py:15-113):select_speaker()(_swarm_group_chat.py:82-98) walksthreadin reverse looking for the most recentHandoffMessageand returns its.targetfield; if none exists, the current speaker continues.validate_group_state()(_swarm_group_chat.py:47-73) enforces that any handoff target is a real participant name, with an explicit error message pointing atHandoffTerminationresume semantics — this is the mechanismSwarm([agent], ...)uses for human-in-the-loop handback (README’sHandoffTerminationexample,README.mdquickstart references this pattern inagents/_assistant_agent.py’shandoffs=parameter).
A fourth, more elaborate manager exists for MagenticOneGroupChat (state schema at state/_states.py:64-72: task, facts, plan, n_rounds, n_stalls — a planning-and-replanning orchestrator, not read in depth for this profile) under teams/_group_chat/_magentic_one/.
Conversation-state persistence, end to end
Three nested layers, each with its own save_state/load_state pair:
ChatCompletionContext(autogen-core/src/autogen_core/model_context/_chat_completion_context.py:10-75) is the lowest layer: an abstract base holdingself._messages: List[LLMMessage], withadd_message()(line 55),get_messages()(abstract, line 60, implemented differently byUnboundedChatCompletionContext,BufferedChatCompletionContext,HeadAndTailChatCompletionContext,TokenLimitedChatCompletionContext— all in the samemodel_context/directory), andsave_state()/load_state()(lines 66-70) that just round-tripChatCompletionContextState(messages=self._messages).AssistantAgent.save_state()(autogen-agentchat/src/autogen_agentchat/agents/_assistant_agent.py:1630-1633) wraps that context dump inAssistantAgentState(llm_context=model_context_state)(schema atstate/_states.py:13-17);load_state()(_assistant_agent.py:1635-1639) validates and forwardsllm_contextback intoself._model_context.load_state(...).ChatAgentContainer.save_state()(_chat_agent_container.py:197-202) wraps the delegate agent’s state plus its own pending_message_buffer(messages received but not yet delivered to the agent) inChatAgentContainerState(state/_states.py:35-40) — this is the layer that exists purely because of the actor-runtime topology: a participant can have buffered-but-unprocessed messages at save time, and those need to round-trip too (_chat_agent_container.py:204-213).BaseGroupChat.save_state()(_base_group_chat.py:748-796) is the outermost layer: it callsself._runtime.agent_save_state(agent_id)— note, the runtime’s method (SingleThreadedAgentRuntime.agent_save_state,autogen-core/src/autogen_core/_single_threaded_agent_runtime.py:880-881, itself just delegating to the agent instance’s ownsave_state()) rather than calling the agent object directly, “because we want to support saving state of remote agents” (comment at_base_group_chat.py:790-791) — the distributed-runtime case is designed in from this layer down, even though the default runtime is single-threaded/in-process.
Group-chat-manager state is saved through the identical agent_save_state path, keyed under self._group_chat_manager_name in the same TeamState.agent_states dict (_base_group_chat.py:793-796) — so a RoundRobinGroupChatManager’s next_speaker_index and a SwarmGroupChatManager’s current_speaker live right alongside participant states in one saved blob, not a separate file.
Tool-calling — native Tool/BaseTool/Workbench first, MCP as an adapter
- The native abstraction predates and is independent of MCP.
Tool(autogen-core/src/autogen_core/tools/_base.py:56-80) is aruntime_checkableProtocolrequiringname,description,schema(aToolSchemaTypedDict, lines 41-46),args_type()/return_type()/state_type(),run_json(), andsave_state_json()/load_state_json().BaseTool(_base.py:96-215) is the concrete ABC most tools subclass;FunctionTool(autogen-core/src/autogen_core/tools/_function_tool.py:30) wraps a plain typed Python function/coroutine, deriving itsToolSchemafrom the function’s type annotations viaargs_base_model_from_signature.Workbench(autogen-core/src/autogen_core/tools/_workbench.py:78-93) is a second, coarser native abstraction — “a set of tools that may share resources and state” — with async-context-manager start/stop lifecycle, used when tools need a live session (browser state, DB connection) rather than being stateless callables. - MCP support lives entirely in
autogen-ext, gated behind an optional extra.autogen-ext/pyproject.toml:153:mcp = ["mcp>=1.11.0"]— themcpPyPI package is not a hard dependency ofautogen-ext, matching the README’s install instructionpip install -U "autogen-agentchat" "autogen-ext[openai]"(core install has no MCP) and the MCP quickstart’s explicitpip install "autogen-ext[mcp]"-equivalent framing (README.md:65-102). - The bridge direction: MCP tools become native
Tools, not the reverse.McpToolAdapter(BaseTool[BaseModel, Any], ABC, Generic[TServerParams])(autogen-ext/src/autogen_ext/tools/mcp/_factory.py:29-56) takes anmcp.Tool(the MCP SDK’s tool descriptor) and anMcpServerParams, converts the tool’s JSON schema to a Pydantic model viaschema_to_pydantic_model(line 50), and implementsrun()(_factory.py:57-83) by opening (or reusing) anmcp.ClientSessionand callingsession.call_tool(...). Three concrete subclasses exist per transport —StdioMcpToolAdapter,SseMcpToolAdapter,StreamableHttpMcpToolAdapter(files_stdio.py,_sse.py,_streamable_http.py) — all produced by the single factory functionmcp_server_tools()(autogen-ext/src/autogen_ext/tools/mcp/_factory.py:10-214), which lists the server’s tools and returns one adapter instance per tool, ready to drop straight into anAssistantAgent(tools=[...])list alongside plainFunctionTools. McpWorkbenchmirrors this at the coarserWorkbenchlayer.McpWorkbench(Workbench, Component[McpWorkbenchConfig])(autogen-ext/src/autogen_ext/tools/mcp/_workbench.py:47) wraps an entire MCP server (not just one tool) and additionally supports MCP Resources, Prompts, Sampling, Roots, and Elicitation (table at_workbench.py:59-78) via an optionalMcpSessionHost— this is the object used in the README’s own MCP quickstart (README.md:75,87,McpWorkbench(server_params)passed asworkbench=mcptoAssistantAgent), i.e., the README itself demonstrates the workbench path, not the rawmcp_server_tools()adapter path, as the recommended MCP entry point.- Net finding: both a native tool abstraction and MCP support exist, and they are not peers — MCP is deliberately downstream of and conformant to the native
Tool/Workbenchprotocols, which is why anAssistantAgent’stools=list can freely mixFunctionTool,McpToolAdapterinstances, andAgentTool(agent-as-tool,autogen_agentchat.tools.AgentTool, used in the README’s multi-agent orchestration example,README.md:106-151) without the agent code needing to know which is which.
What’s inside this submodule
| Path | What’s there |
|---|---|
python/packages/autogen-core/src/autogen_core/tools/_base.py | Tool protocol, BaseTool, BaseStreamTool, BaseToolWithState — the native tool abstraction |
python/packages/autogen-core/src/autogen_core/tools/_function_tool.py | FunctionTool — wraps a typed Python function as a native tool |
python/packages/autogen-core/src/autogen_core/tools/_workbench.py | Workbench ABC — coarser, stateful/session-based tool grouping, native, MCP-agnostic |
python/packages/autogen-core/src/autogen_core/model_context/ | ChatCompletionContext + 4 concrete strategies (unbounded/buffered/head-tail/token-limited) — the per-agent LLM message store |
python/packages/autogen-core/src/autogen_core/_single_threaded_agent_runtime.py | SingleThreadedAgentRuntime — default in-process actor runtime; agent_save_state/agent_load_state |
python/packages/autogen-agentchat/src/autogen_agentchat/teams/_group_chat/_base_group_chat.py | BaseGroupChat — topic wiring, run/run_stream, team-level save_state/load_state, pause/resume |
python/packages/autogen-agentchat/src/autogen_agentchat/teams/_group_chat/_base_group_chat_manager.py | BaseGroupChatManager — shared turn-taking scaffolding, select_speaker as the abstract seam |
.../_round_robin_group_chat.py | RoundRobinGroupChatManager/RoundRobinGroupChat — modular-index speaker selection |
.../_selector_group_chat.py | SelectorGroupChatManager/SelectorGroupChat — LLM-prompted speaker selection, selector_func/candidate_func overrides |
.../_swarm_group_chat.py | SwarmGroupChatManager/Swarm — handoff-message-target speaker selection |
.../_magentic_one/ | MagenticOneGroupChat — planning/replanning orchestrator (task/facts/plan/n_stalls state) |
.../_chat_agent_container.py | ChatAgentContainer — wraps one ChatAgent/Team as a runtime actor; per-participant message buffer + state |
python/packages/autogen-agentchat/src/autogen_agentchat/state/_states.py | Every *State pydantic schema — AssistantAgentState, TeamState, RoundRobinManagerState, SelectorManagerState, SwarmManagerState, MagenticOneOrchestratorState |
python/packages/autogen-ext/src/autogen_ext/tools/mcp/_factory.py | mcp_server_tools() + McpToolAdapter base — MCP-tool-to-native-Tool bridge |
python/packages/autogen-ext/src/autogen_ext/tools/mcp/_workbench.py | McpWorkbench — MCP-backed implementation of the native Workbench ABC |
python/packages/autogen-ext/src/autogen_ext/tools/mcp/{_stdio,_sse,_streamable_http}.py | Transport-specific MCP tool adapters |
python/packages/autogen-studio/ | No-code GUI for prototyping multi-agent workflows (not production-ready per README caution) |
python/packages/agbench/ | AutoGen Bench — benchmarking suite |
python/packages/magentic-one-cli/ | Reference Magentic-One multi-agent team (web browsing, code exec, file handling) |
python/packages/pyautogen/ | Legacy 0.2.x monolithic package, kept for migration reference — not the architecture this profile describes |
dotnet/ | Parallel .NET implementation (Microsoft.AutoGen.* NuGet packages), not read for this profile |
LICENSE / LICENSE-CODE | CC-BY-4.0 (docs) / MIT (code) — confirmed by direct read, not assumed |
If you read three files: autogen-agentchat/src/autogen_agentchat/teams/_group_chat/_base_group_chat_manager.py (the shared turn-taking scaffold and the select_speaker seam), autogen-agentchat/src/autogen_agentchat/teams/_group_chat/_selector_group_chat.py (the most elaborate of the three concrete managers — LLM-prompted selection with retry/fallback), and autogen-ext/src/autogen_ext/tools/mcp/_factory.py (the exact shape of the MCP-to-native-tool bridge).
Mental model for using it well
- Pick the team type by how turn order should be decided, not by feature checklist: Round Robin for deterministic fixed-order collaboration, Selector for LLM-judged dynamic routing among heterogeneous specialist agents, Swarm for explicit agent-declared handoffs (including human-in-the-loop via
HandoffTermination). - Treat
save_state()/load_state()on aTeamas the unit of durability, not per-agent state in isolation — the nestedTeamState.agent_statesdict is what actually round-trips a paused conversation, including exactly whose turn is next. - Reach for
Workbench/McpWorkbenchwhen tools share session state (browser, DB connection); reach for plainFunctionTool/McpToolAdapterlist entries when tools are stateless — both compose in the sametools=/workbench=agent constructor arguments. - A nested
Teamis a valid participant of anotherTeam—ChatAgentContainertreatsTeamandChatAgentparticipants uniformly via anisinstancecheck, so hierarchical team-of-teams composition is a first-class pattern, not a workaround. - Given the maintenance-mode status (
README.md:18-25), treat new capability gaps as permanent unless the community fork/PR path resolves them — Microsoft’s own guidance is to start new work on Microsoft Agent Framework instead.
When NOT to reach for this
- You’re starting a new project today. The README’s own caution (
README.md:18-25,200-202) is unambiguous: AutoGen is community-managed, bug-fixes-and-security-only, and Microsoft explicitly points new users at Microsoft Agent Framework. - You want a single-agent CLI harness with its own MCP client as the primary interaction mode. AutoGen’s unit of work is the multi-agent
Team; a singleAssistantAgentworks but the framework’s actual design weight (topics,ChatAgentContainer, group-chat managers) is multi-agent-first. - You need production-hardened authn/security out of the box for a no-code surface. AutoGen Studio is explicitly flagged as prototyping-only, not production-ready (
README.md:160-164). - You want tool-calling to be MCP with no native abstraction underneath. AutoGen’s tool layer is native-first (
Tool/BaseTool/Workbenchpredate MCP support in this codebase); if the entire point of adoption is “we standardize purely on MCP as the tool contract,” the native layer is an extra abstraction to reason around, not a pure pass-through. - You need a distributed, multi-process runtime out of the box. The default
BaseGroupChatruntime isSingleThreadedAgentRuntime(in-process); the “remote agent” and distributed runtime path is designed for (agent_save_state’s docstring comment) but a separate, heavier setup than the default.
How this compares to the rest of the study
| Axis | OpenHands | opencode | AutoGen |
|---|---|---|---|
| Shape | Multi-agent, event-stream trace architecture | CLI-first single-agent, own MCP client | Python framework, layered core/agentchat/ext, multi-agent teams as the primary unit |
| Status | Actively developed | Actively developed | Maintenance mode — bug fixes/security only (README.md:18-25) |
| Multi-agent orchestration | Event-stream-driven agent delegation | Not the primary unit (single-agent) | First-class: Team abstraction with 4 concrete turn-taking algorithms (Round Robin, Selector/LLM, Swarm/handoff, Magentic-One/planner) |
| Turn-taking mechanism | Not profiled here | N/A | Pluggable select_speaker() per manager — modular index, LLM-prompted with retry, or handoff-message-target scan |
| Conversation-state persistence | Not profiled here | Own session storage | Nested, name-keyed TeamState.agent_states dict; each agent’s ChatCompletionContext messages + manager-specific turn state (e.g. next_speaker_index) |
| Tool-calling model | Not profiled here | Own MCP client, structured tool-call loop | Native-first: Tool/BaseTool/Workbench protocol predates MCP; MCP bridged in via McpToolAdapter/McpWorkbench behind an optional [mcp] extra |
| Runtime substrate | Event stream | Process/session model | Actor-style AgentRuntime (topics + subscriptions); default is in-process SingleThreadedAgentRuntime, designed for remote/distributed extension |
| Best fit | Tracing/observability-centric agentic coding | Terminal-first agent work, swappable MCP toolset | Existing AutoGen deployments, or new work specifically wanting a multi-agent team with an explicit, swappable speaker-selection algorithm — with eyes open about maintenance-mode status |
The axis AutoGen owns in this study is turn-taking as an explicit, swappable algorithm with its own serializable state — no other entry in this study exposes “how the group decides who talks next” as a single overridable method (select_speaker) with three (four, counting Magentic-One) shipped implementations and per-implementation state schemas that round-trip through a uniform team-level save_state/load_state contract.
One-line summary
AutoGen (0.7.5, layered
autogen-core/autogen-agentchat/autogen-ext, now in Microsoft maintenance mode perREADME.md:18-25) runs multi-agent teams as actor-runtime topic graphs whereBaseGroupChatManager.select_speaker()is the one seam three built-in team types override —RoundRobinGroupChatManager’s modular index,SelectorGroupChatManager’s LLM-prompted role selection with regex-mention parsing and retry/fallback, andSwarmGroupChatManager’s handoff-message-target scan — while conversation state persists as aTeamState.agent_statesdict keyed by participant name (not runtime ID, deliberately decoupled since v0.4.9) nesting each agent’s serializedChatCompletionContextplus manager-specific turn-tracking fields; and tool-calling is native-first (Tool/BaseTool/FunctionTool/Workbenchinautogen-core, predating MCP) with Model Context Protocol support bridged in as an adapter layer (McpToolAdapter,McpWorkbench) behind an optionalautogen-ext[mcp]extra, not the framework’s foundational tool contract.