Migration · Group Chat
This page is for users porting an existing AG2 deployment that uses GroupChat plus Handoffs-style orchestration onto the new autogen.beta.network module. The modern equivalent is the WorkflowAdapter, driven by a declarative TransitionGraph.
The translation is mostly mechanical. The two systems share the same vocabulary — speakers, conditions, targets, terminations — but the new one is data-first (a TransitionGraph is JSON-serialisable and survives Hub.hydrate()) and runs on top of the network's hub-and-spoke architecture instead of an in-process turn manager.
For per-pattern translations of the canonical orchestrations (Pipeline, Star, Feedback Loop, Triage-with-Tasks, etc.), jump to the Pattern Cookbook.
Concept Mapping#
| Classic (non-beta) concept | Beta network equivalent | Notes |
|---|---|---|
GroupChat(agents=[...]) | A workflow session with the agents as participants | The hub plays the role of GroupChatManager. |
GroupChatManager | The WorkflowAdapter + Hub | Turn-taking enforcement moves into the hub; the adapter validates each send against the graph. |
Agent.handoffs | A TransitionGraph (per-session, not per-agent) | Handoffs are described once at the session level — easier to reason about and serialisable. |
AgentTarget(agent) | AgentTarget(agent_id) | Same name; takes an agent_id instead of an agent reference. |
RevertToInitiator() | RevertToInitiatorTarget() | Identical semantics. |
Stay() | StayTarget() | Identical. |
Terminate() | TerminateTarget(reason="…") | Now carries an explicit reason that lands on EV_SESSION_CLOSED. |
OnContextCondition(...) | A custom TransitionCondition | Implement the Protocol; register via register_condition(...). |
OnCondition(...) | A custom TransitionCondition | Same. |
ReplyResult(target=...) from a tool | EV_HANDOFF envelope, paired with ToolCalled("...") | The default handler emits EV_HANDOFF automatically when a tool's result implies a handoff. |
FunctionTarget(fn) | A custom TransitionTarget whose resolve() runs Python | Same pattern; register via register_target(...). |
max_round=N | TransitionGraph(..., max_turns=N) | Same hard cap. |
Side-by-Side: Round Robin#
Classic (non-beta):
Beta network:
The auto-termination on max_turns lands on EV_SESSION_CLOSED with reason="round_robin_complete".
Note
discussion is also a round-robin session — but it never auto-terminates and has no turn cap. Use it when the application decides when to stop. Use workflow + TransitionGraph.round_robin when you want a hard cap and an EV_SESSION_CLOSED to wait on.
Side-by-Side: Pipeline / Sequential#
Classic:
Beta network:
Auto-terminates with reason="sequence_complete".
Side-by-Side: Conditional Handoff#
A common classic pattern: a triage agent inspects the user request and routes to a specialist via a tool call.
Classic:
Beta network:
The triage agent has its own @triage_agent.tool definition for escalate; the default handler emits an EV_HANDOFF envelope when the tool returns and the next-speaker rule sees ToolCalled("escalate") match.
Tip
Tool-driven handoffs (ToolCalled(...)) are the cleanest migration path for ReplyResult-style classic logic. Define one tool per branch you want the LLM to take, and write a ToolCalled transition for each.
Side-by-Side: Function Target / Custom Routing#
FunctionTarget(fn) runs Python to decide who's next. The new equivalent is a custom TransitionTarget.
Classic:
Beta network:
The custom target dataclass is JSON-serialisable, so it round-trips through TransitionGraph.to_dict() and survives Hub.hydrate(). That's the win over FunctionTarget: state is data, not closures.
What's Different (Beyond the Translation)#
A few changes that will affect how you architect the migration:
- The hub is authoritative. Turn-taking, state, audit, and expectation enforcement all live in the hub. Your agent code becomes thinner.
- Sessions have ids. Every
alice.open(...)returns a session id. Audit, replay, and inspection are scoped to it. - Default handlers do most of the heavy lifting. When agents have
attach_plugin=True, you don't write any "agent receives message → run LLM → send reply" glue. The framework handles it. - Sub-task observation comes for free. Calls to
agent.task(..., capability="X")inside a network turn auto-updateResume.observed["X"]. There was no equivalent in the classic world. See Task Observation. - Expectations replace
max_roundexhaustively. You can declarereply_within(60s, auto_close)per session type, get a violation logged to the audit log, and have the session auto-close — see Expectations & Audit. - Views replace ad-hoc context windowing. Each adapter has a default view (
FullTranscript,WindowedSummary); custom views plug in via theViewPolicyProtocol — see Views & Skills. - The transport is pluggable.
LocalLinkis the in-process default, but the link-layer is a Protocol — cross-process and cross-host transports are drop-in replacements.
Migration Checklist#
- Stand up a hub:
hub = await Hub.open(MemoryKnowledgeStore()). (Pick aKnowledgeStorebased on whether you need persistence.) - Wrap each existing
Agentin anAgentClientviahc.register(...). Provide aPassport(name) and aResume(claimed capabilities). - Translate your
Handoffsconfiguration into aTransitionGraph. Useround_robin/sequencefactories where they fit; build the graph manually for conditional logic. - Replace
initiate_chat(...)withalice.open(type="workflow", target=[...], knobs={"graph": graph.to_dict()})followed bysession.send(text). - Wait for termination with
alice.wait_for_session_event(session_id=..., predicate=lambda e: e.event_type == EV_SESSION_CLOSED). - Inspect the WAL via
hub.read_wal(session_id)for replay and the audit log viahub._audit_log.read_all()for governance.
For a runnable end-to-end translation of the "researcher → writer → editor" handoff pattern, see the Pipeline entry in the Pattern Cookbook.