Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -275,6 +275,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)
Expand All @@ -288,7 +291,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(
Expand Down Expand Up @@ -938,6 +943,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 {}
Expand Down
34 changes: 34 additions & 0 deletions python/packages/orchestrations/tests/test_handoff.py
Original file line number Diff line number Diff line change
Expand Up @@ -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")
Expand Down
Loading