Skip to content

fix(agentchat): respect allow_repeated_speaker=False in selector fallback#7568

Open
extrasmall0 wants to merge 1 commit intomicrosoft:mainfrom
extrasmall0:fix/selector-group-chat-livelock
Open

fix(agentchat): respect allow_repeated_speaker=False in selector fallback#7568
extrasmall0 wants to merge 1 commit intomicrosoft:mainfrom
extrasmall0:fix/selector-group-chat-livelock

Conversation

@extrasmall0
Copy link
Copy Markdown

Fixes #7471

Problem

When allow_repeated_speaker=False and the model exhausts max_selector_attempts without picking a valid (non-previous) speaker, the fallback in _select_speaker returns self._previous_speaker:

if self._previous_speaker is not None:
    trace_logger.warning(...)
    return self._previous_speaker  # ← returns the excluded speaker

This creates a livelock: agent A speaks → selector tries to pick someone else → exhausts attempts → falls back to A → A speaks again → repeat.

Root Cause

The _select_speaker method receives a participants list that already has the previous speaker filtered out (done by the caller when allow_repeated_speaker=False). The fallback logic ignores this filtered list and reads directly from self._previous_speaker.

Fix

Add an early-exit branch before the existing fallback: when allow_repeated_speaker=False and there is a previous speaker, use random.choice(participants) (the already-filtered list) instead of returning the excluded speaker.

if self._previous_speaker is not None and not self._allow_repeated_speaker:
    # participants already excludes the previous speaker
    return random.choice(participants)

The existing fallback for allow_repeated_speaker=True (return previous speaker) is unchanged.

…back

When the model fails to select a valid speaker within max_selector_attempts
and allow_repeated_speaker=False, the fallback unconditionally returned
self._previous_speaker — the exact speaker that should be excluded — causing
a livelock where the same agent kept being selected indefinitely.

The _select_speaker method already receives a participants list that has the
previous speaker filtered out (done by the caller at line 196-197), so the
fix is to use random.choice(participants) for the fallback when repeated
speakers are not allowed, instead of falling through to the
self._previous_speaker branch.
@extrasmall0
Copy link
Copy Markdown
Author

extrasmall0 commented Apr 11, 2026

Quick follow-up: this is ready for review, and I wanted to add one concrete bit of context in case it helps triage. The regression happens in the selector fallback path when allow_repeated_speaker=False: after candidate filtering, the fallback can still pick a previously seen speaker instead of respecting the no-repeat setting. This patch keeps the existing selector flow intact and only fixes that fallback behavior. Happy to add a narrower regression test or trim the change further if that would make review easier.

@extrasmall0
Copy link
Copy Markdown
Author

extrasmall0 commented Apr 18, 2026

Quick follow-up after another week in queue: this is still ready for review, and the scope remains intentionally narrow. The change only fixes the selector fallback path when allow_repeated_speaker=False, so repeated speakers cannot slip back in after candidate filtering. There are no functional changes outside that fallback path. If it helps triage, I can split out the regression test or trim the patch further.

@extrasmall0
Copy link
Copy Markdown
Author

extrasmall0 commented Apr 23, 2026

Quick follow-up after rechecking this against current main: the patch is still a small, isolated fallback fix in the selector path. It only changes the exhausted selector fallback when allow_repeated_speaker=False, so instead of returning the previously excluded speaker it chooses from the already-filtered participant list. If it helps review, I can also add a focused regression test around the exhausted-selector path.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Bug] SelectorGroupChat livelock: fallback returns excluded previous speaker when allow_repeated_speaker=False

1 participant