From dcf2f073a31f03481fb32f50f7a2c7569371fb83 Mon Sep 17 00:00:00 2001 From: Lengshuang <90967079+Lesereingrape@users.noreply.github.com> Date: Fri, 2 Oct 2026 13:25:13 +0800 Subject: [PATCH] Python: Do not turn a zero handoff turn limit into the default HandoffAgentExecutor folded autonomous_mode_turn_limit through `or`, so an explicit 0 became the default 50 and the agent kept running on injected prompts instead of returning control to the user. --- .../_handoff.py | 9 ++++- .../orchestrations/tests/test_handoff.py | 34 +++++++++++++++++++ 2 files changed, 42 insertions(+), 1 deletion(-) diff --git a/python/packages/orchestrations/agent_framework_orchestrations/_handoff.py b/python/packages/orchestrations/agent_framework_orchestrations/_handoff.py index aadcb8ad6df..b9bf7f48f28 100644 --- a/python/packages/orchestrations/agent_framework_orchestrations/_handoff.py +++ b/python/packages/orchestrations/agent_framework_orchestrations/_handoff.py @@ -266,6 +266,9 @@ def __init__( autonomous_mode_prompt: Prompt to provide to the agent when continuing in autonomous mode. This will guide the agent in the absence of user input. autonomous_mode_turn_limit: Maximum number of autonomous turns before requesting user input. + ``0`` returns control to the user as soon as the agent responds + without a handoff; the default limit is only used when this is + left as ``None``. """ self._auto_handoff_middleware = _AutoHandoffMiddleware(handoffs) cloned_agent = self._prepare_agent_with_handoffs(agent, handoffs, self._auto_handoff_middleware) @@ -279,7 +282,9 @@ def __init__( # Autonomous mode members self._autonomous_mode = autonomous_mode self._autonomous_mode_prompt = autonomous_mode_prompt or _AUTONOMOUS_MODE_DEFAULT_PROMPT - self._autonomous_mode_turn_limit = autonomous_mode_turn_limit or _DEFAULT_AUTONOMOUS_TURN_LIMIT + self._autonomous_mode_turn_limit = ( + _DEFAULT_AUTONOMOUS_TURN_LIMIT if autonomous_mode_turn_limit is None else autonomous_mode_turn_limit + ) self._autonomous_mode_turns = 0 def _prepare_agent_with_handoffs( @@ -928,6 +933,8 @@ def with_autonomous_mode( in autonomous mode. If not provided, a default prompt will be used. turn_limits: Optional mapping of agent identifiers/factory names to maximum number of autonomous turns before returning control to the user. If not provided, a default turn limit will be used. + A limit of ``0`` disables autonomous continuation for that agent instead of falling back + to the default. """ self._autonomous_mode = True self._autonomous_mode_prompts = prompts or {} diff --git a/python/packages/orchestrations/tests/test_handoff.py b/python/packages/orchestrations/tests/test_handoff.py index 781e64b73b9..c3b5c19b281 100644 --- a/python/packages/orchestrations/tests/test_handoff.py +++ b/python/packages/orchestrations/tests/test_handoff.py @@ -1327,6 +1327,40 @@ async def test_autonomous_mode_resumes_user_input_on_turn_limit(): assert requests[0].source_executor_id == worker.name +async def test_autonomous_mode_zero_turn_limit_returns_control_immediately(): + """A turn limit of 0 means no autonomous continuation, not the default turn limit.""" + triage_client = MockChatClient(name="triage", handoff_to="worker") + worker_client = MockChatClient(name="worker") + triage: Agent[Any] = Agent( + id="triage", + name="triage", + client=triage_client, + require_per_service_call_history_persistence=True, + ) + worker: Agent[Any] = Agent( + id="worker", + name="worker", + client=worker_client, + require_per_service_call_history_persistence=True, + ) + + workflow = ( + HandoffBuilder(participants=_as_handoff_agents(triage, worker), termination_condition=lambda _: False) + .with_start_agent(_as_handoff_agent(triage)) + .with_autonomous_mode(agents=[worker], turn_limits={resolve_agent_id(worker): 0}) + .build() + ) + + events = await _drain(workflow.run("Start", stream=True)) + + # Swallowing 0 into the default limit lets the agent run on for 50 injected prompts, + # so the worker's client being called once is what distinguishes the two readings. + assert len(worker_client.received_messages) == 1, "A zero turn limit must not grant autonomous turns" + requests = [ev for ev in events if ev.type == "request_info"] + assert requests and len(requests) == 1, "A zero turn limit should request user input at once" + assert requests[0].source_executor_id == worker.name + + def test_build_fails_without_start_agent(): """Verify that build() raises ValueError when with_start_agent() was not called.""" triage = MockHandoffAgent(name="triage")