Skip to content

Commit 89ec3fc

Browse files
fix: prevent infinite loop when sign-off validation rejects finish
When Coordinator repeatedly says finish=true but sign-off validation fails, the selection_func routed back to Coordinator creating a 100-round loop. Fix: track consecutive finish rejections and force-terminate after 5 attempts. Also reset counter when Coordinator routes to a participant. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
1 parent c5c3a34 commit 89ec3fc

1 file changed

Lines changed: 32 additions & 1 deletion

File tree

src/processor/src/libs/agent_framework/groupchat_orchestrator.py

Lines changed: 32 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -310,6 +310,12 @@ def __init__(
310310
# Snapshot of progress_counter at the time we last saw _last_coordinator_selection.
311311
self._last_coordinator_selection_progress: int = 0
312312

313+
# Track consecutive finish rejections (sign-off validation failures).
314+
# When the Coordinator keeps saying finish=true but sign-offs are
315+
# incomplete, prevent infinite loops by force-terminating after a threshold.
316+
self._finish_rejection_count: int = 0
317+
self._max_finish_rejections: int = 5
318+
313319
def _request_forced_termination(
314320
self, *, reason: str, termination_type: str
315321
) -> None:
@@ -1278,10 +1284,26 @@ async def _complete_agent_response(
12781284
)
12791285
is_valid, reason = self._validate_sign_offs(sign_off_conversation)
12801286
if not is_valid:
1287+
self._finish_rejection_count += 1
12811288
logger.warning(
1282-
"Termination rejected for success completion: %s. Workflow continues.",
1289+
"Termination rejected for success completion (%d/%d): %s. Workflow continues.",
1290+
self._finish_rejection_count,
1291+
self._max_finish_rejections,
12831292
reason,
12841293
)
1294+
if self._finish_rejection_count >= self._max_finish_rejections:
1295+
logger.warning(
1296+
"Max finish rejections reached (%d). Force-terminating.",
1297+
self._finish_rejection_count,
1298+
)
1299+
self._request_forced_termination(
1300+
reason=(
1301+
f"Coordinator attempted finish {self._finish_rejection_count} times "
1302+
f"but sign-off validation kept failing: {reason}"
1303+
),
1304+
termination_type="hard_timeout",
1305+
)
1306+
return
12851307
# Do NOT set _termination_requested.
12861308
return
12871309

@@ -1297,6 +1319,9 @@ async def _complete_agent_response(
12971319
and selected
12981320
and selected.lower() != "none"
12991321
):
1322+
# Non-finish routing: reset finish rejection counter since
1323+
# Coordinator is doing productive work (selecting participants).
1324+
self._finish_rejection_count = 0
13001325
# Record invocation time for non-termination coordinator selections
13011326
self._agent_invoked_at[selected] = completed_at
13021327
except Exception as exc:
@@ -1396,6 +1421,12 @@ def selection_func(state: GroupChatState) -> str:
13961421
and selected in state.participants
13971422
):
13981423
return selected
1424+
1425+
# Coordinator said finish=true with no valid participant.
1426+
# Route back to Coordinator to let the streaming loop's
1427+
# termination/force-termination logic handle it.
1428+
if manager_response.finish is True:
1429+
return coordinator_name
13991430
except Exception:
14001431
pass
14011432

0 commit comments

Comments
 (0)