From ce9f6712ffafda485cdb2f00ac21fae3eaf40e53 Mon Sep 17 00:00:00 2001 From: Soju06 Date: Tue, 14 Jul 2026 05:30:59 +0000 Subject: [PATCH] refactor(agent): remove the inter-tool delay MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The 1.0s sleep between sequential tool calls has been present since the initial commit with no documented rationale. It sleeps between local tool executions — the next LLM request goes out only after the whole batch — so it rate-limits nothing, and the parallel read-only path already runs with no delay. Every multi-tool turn pays (N-1) seconds of dead time. Remove the sleep, the internal tool_delay plumbing, and dead test assignments. AIAgent.__init__ keeps tool_delay as a deprecated no-op keyword for one release so existing programmatic callers construct cleanly; passing it emits a DeprecationWarning. --- agent/agent_init.py | 7 +--- agent/tool_executor.py | 3 -- run_agent.py | 13 +++++-- .../test_1630_context_overflow_loop.py | 1 - tests/run_agent/test_413_compression.py | 1 - .../test_conversation_fallback_state.py | 2 - .../test_malformed_tool_arguments.py | 1 - .../test_nonretryable_error_html_summary.py | 1 - .../test_partial_stream_finish_reason.py | 1 - tests/run_agent/test_run_agent.py | 38 +++++++++++++++++-- .../test_tool_call_guardrail_runtime.py | 1 - .../test_tool_call_incremental_persistence.py | 1 - .../test_turn_completion_explainer.py | 1 - 13 files changed, 47 insertions(+), 24 deletions(-) diff --git a/agent/agent_init.py b/agent/agent_init.py index ea473632c6a..d8eee9497ca 100644 --- a/agent/agent_init.py +++ b/agent/agent_init.py @@ -455,8 +455,7 @@ def init_agent( command: str = None, args: list[str] | None = None, model: str = "", - max_iterations: int = 500, # Default tool-calling iterations (shared with subagents) - tool_delay: float = 1.0, + max_iterations: int = 90, # Default tool-calling iterations (shared with subagents) enabled_toolsets: List[str] = None, disabled_toolsets: List[str] = None, save_trajectories: bool = False, @@ -529,8 +528,7 @@ def init_agent( requested_provider (str): Original provider identity before runtime canonicalization api_mode (str): API mode override: "chat_completions" or "codex_responses" model (str): Model name to use (default: "anthropic/claude-opus-4.6") - max_iterations (int): Maximum number of tool calling iterations (default: 500) - tool_delay (float): Delay between tool calls in seconds (default: 1.0) + max_iterations (int): Maximum number of tool calling iterations (default: 90) enabled_toolsets (List[str]): Only enable tools from these toolsets (optional) disabled_toolsets (List[str]): Disable tools from these toolsets (optional) save_trajectories (bool): Whether to save conversation trajectories to JSONL files (default: False) @@ -576,7 +574,6 @@ def init_agent( # Shared iteration budget — parent creates, children inherit. # Consumed by every LLM turn across parent + all subagents. agent.iteration_budget = iteration_budget or IterationBudget(max_iterations) - agent.tool_delay = tool_delay agent.save_trajectories = save_trajectories agent.verbose_logging = verbose_logging agent.quiet_mode = quiet_mode diff --git a/agent/tool_executor.py b/agent/tool_executor.py index 688d5720df2..f8bf5e6b2e5 100644 --- a/agent/tool_executor.py +++ b/agent/tool_executor.py @@ -1979,9 +1979,6 @@ def execute_tool_calls_sequential(agent, assistant_message, messages: list, effe return break - if agent.tool_delay > 0 and i < len(assistant_message.tool_calls): - time.sleep(agent.tool_delay) - # ── Per-turn aggregate budget enforcement ───────────────────────── num_tools_seq = len(assistant_message.tool_calls) if finalize and num_tools_seq > 0: diff --git a/run_agent.py b/run_agent.py index 5d9af78815c..522579438d9 100644 --- a/run_agent.py +++ b/run_agent.py @@ -45,6 +45,7 @@ import tempfile import time import threading import uuid +import warnings from typing import List, Dict, Any, Optional, Callable # NOTE: `from openai import OpenAI` is deliberately NOT at module top — the # SDK pulls ~240 ms of imports. We expose `OpenAI` as a thin proxy object @@ -439,8 +440,8 @@ class AIAgent: command: str = None, args: list[str] | None = None, model: str = "", - max_iterations: int = 500, # Default tool-calling iterations (shared with subagents) - tool_delay: float = 1.0, + max_iterations: int = 90, # Default tool-calling iterations (shared with subagents) + tool_delay: float = None, # Deprecated: accepted for compatibility, ignored enabled_toolsets: List[str] = None, disabled_toolsets: List[str] = None, save_trajectories: bool = False, @@ -504,6 +505,13 @@ class AIAgent: requested_provider: str = None, ): """Forwarder — see ``agent.agent_init.init_agent``.""" + if tool_delay is not None: + warnings.warn( + "tool_delay is deprecated and ignored; sequential tool calls " + "no longer sleep between executions.", + DeprecationWarning, + stacklevel=2, + ) from agent.agent_init import init_agent init_agent( self, @@ -518,7 +526,6 @@ class AIAgent: args=args, model=model, max_iterations=max_iterations, - tool_delay=tool_delay, enabled_toolsets=enabled_toolsets, disabled_toolsets=disabled_toolsets, save_trajectories=save_trajectories, diff --git a/tests/run_agent/test_1630_context_overflow_loop.py b/tests/run_agent/test_1630_context_overflow_loop.py index 3e5e3d0cfdf..ed0446ae56d 100644 --- a/tests/run_agent/test_1630_context_overflow_loop.py +++ b/tests/run_agent/test_1630_context_overflow_loop.py @@ -38,7 +38,6 @@ class TestGeneric400Heuristic: a.client = MagicMock() a._cached_system_prompt = "You are helpful." a._use_prompt_caching = False - a.tool_delay = 0 a.compression_enabled = False return a diff --git a/tests/run_agent/test_413_compression.py b/tests/run_agent/test_413_compression.py index cf05a9a9054..35cfefe22ee 100644 --- a/tests/run_agent/test_413_compression.py +++ b/tests/run_agent/test_413_compression.py @@ -94,7 +94,6 @@ def agent(): a.client = MagicMock() a._cached_system_prompt = "You are helpful." a._use_prompt_caching = False - a.tool_delay = 0 # Default matches production (`compression.enabled` defaults to True). # Overflow-recovery tests below verify that 413 / context-overflow # errors DO trigger compression; the disabled-path behavior is diff --git a/tests/run_agent/test_conversation_fallback_state.py b/tests/run_agent/test_conversation_fallback_state.py index c92cee2937c..edea8af8872 100644 --- a/tests/run_agent/test_conversation_fallback_state.py +++ b/tests/run_agent/test_conversation_fallback_state.py @@ -80,7 +80,6 @@ def test_substantive_tool_only_turn_invalidates_older_housekeeping_fallback(): agent._cached_system_prompt = "You are helpful." agent._use_prompt_caching = False - agent.tool_delay = 0 agent.compression_enabled = False agent.save_trajectories = False agent.valid_tool_names = {"todo", "web_search"} @@ -147,7 +146,6 @@ def test_housekeeping_only_turn_still_sets_fallback(): agent._cached_system_prompt = "You are helpful." agent._use_prompt_caching = False - agent.tool_delay = 0 agent.compression_enabled = False agent.save_trajectories = False agent.valid_tool_names = {"memory"} diff --git a/tests/run_agent/test_malformed_tool_arguments.py b/tests/run_agent/test_malformed_tool_arguments.py index 6736182d304..311556dc3a3 100644 --- a/tests/run_agent/test_malformed_tool_arguments.py +++ b/tests/run_agent/test_malformed_tool_arguments.py @@ -34,7 +34,6 @@ def _make_agent() -> AIAgent: skip_memory=True, ) agent.client = MagicMock() - agent.tool_delay = 0 agent._flush_messages_to_session_db = MagicMock() return agent diff --git a/tests/run_agent/test_nonretryable_error_html_summary.py b/tests/run_agent/test_nonretryable_error_html_summary.py index db765b124f3..7980547057f 100644 --- a/tests/run_agent/test_nonretryable_error_html_summary.py +++ b/tests/run_agent/test_nonretryable_error_html_summary.py @@ -72,7 +72,6 @@ def _make_agent() -> AIAgent: a.client = MagicMock() a._cached_system_prompt = "You are helpful." a._use_prompt_caching = False - a.tool_delay = 0 a.compression_enabled = False a.save_trajectories = False return a diff --git a/tests/run_agent/test_partial_stream_finish_reason.py b/tests/run_agent/test_partial_stream_finish_reason.py index 05db5b7fcb3..d9aead69164 100644 --- a/tests/run_agent/test_partial_stream_finish_reason.py +++ b/tests/run_agent/test_partial_stream_finish_reason.py @@ -333,7 +333,6 @@ def loop_agent(): a.client = MagicMock() a._cached_system_prompt = "You are helpful." a._use_prompt_caching = False - a.tool_delay = 0 a.compression_enabled = False a.save_trajectories = False return a diff --git a/tests/run_agent/test_run_agent.py b/tests/run_agent/test_run_agent.py index c2b6db704ea..e87c14a3536 100644 --- a/tests/run_agent/test_run_agent.py +++ b/tests/run_agent/test_run_agent.py @@ -978,6 +978,25 @@ class TestInit: assert agent.api_mode == "anthropic_messages" mock_anthropic.Anthropic.assert_called_once() + def test_tool_delay_kwarg_is_deprecated_noop(self): + """tool_delay stays accepted for compatibility but warns and is ignored.""" + with ( + patch("run_agent.get_tool_definitions", return_value=[]), + patch("run_agent.check_toolset_requirements", return_value={}), + patch("run_agent.OpenAI"), + ): + with pytest.warns(DeprecationWarning, match="tool_delay"): + a = AIAgent( + api_key="test-key-1234567890", + base_url="https://openrouter.ai/api/v1", + tool_delay=0, + quiet_mode=True, + skip_context_files=True, + skip_memory=True, + ) + # The value is discarded — nothing downstream reads it anymore. + assert not hasattr(a, "tool_delay") + def test_prompt_caching_claude_openrouter(self): """Claude model via OpenRouter should enable prompt caching.""" with ( @@ -2397,6 +2416,22 @@ class TestExecuteToolCalls: assert messages[0]["role"] == "tool" assert "search result" in messages[0]["content"] + def test_sequential_tool_calls_run_without_delay(self, agent): + """Two sequential tool calls execute back-to-back with no sleep between them.""" + tc1 = _mock_tool_call(name="web_search", arguments="{}", call_id="c1") + tc2 = _mock_tool_call(name="web_search", arguments="{}", call_id="c2") + mock_msg = _mock_assistant_msg(content="", tool_calls=[tc1, tc2]) + messages = [] + with ( + patch("run_agent.handle_function_call", return_value="ok") as mock_hfc, + patch("agent.tool_executor.time.sleep") as mock_sleep, + ): + agent._execute_tool_calls_sequential(mock_msg, messages, "task-1") + assert mock_hfc.call_count == 2 + mock_sleep.assert_not_called() + tool_results = [m for m in messages if m["role"] == "tool"] + assert [m["tool_call_id"] for m in tool_results] == ["c1", "c2"] + def test_sequential_memory_remove_notifies_provider_with_tool_result(self, agent): old_text = "stale preference entry" tc = _mock_tool_call( @@ -4419,7 +4454,6 @@ class TestRunConversation: """Common setup for run_conversation tests.""" agent._cached_system_prompt = "You are helpful." agent._use_prompt_caching = False - agent.tool_delay = 0 agent.compression_enabled = False agent.save_trajectories = False @@ -6702,7 +6736,6 @@ class TestRetryExhaustion: def _setup_agent(self, agent): agent._cached_system_prompt = "You are helpful." agent._use_prompt_caching = False - agent.tool_delay = 0 agent.compression_enabled = False agent.save_trajectories = False @@ -8703,7 +8736,6 @@ class TestReasoningReplayForStrictProviders: def _setup_agent(self, agent): agent._cached_system_prompt = "You are helpful." agent._use_prompt_caching = False - agent.tool_delay = 0 agent.compression_enabled = False agent.save_trajectories = False diff --git a/tests/run_agent/test_tool_call_guardrail_runtime.py b/tests/run_agent/test_tool_call_guardrail_runtime.py index 3bc8a83ab59..95033831517 100644 --- a/tests/run_agent/test_tool_call_guardrail_runtime.py +++ b/tests/run_agent/test_tool_call_guardrail_runtime.py @@ -54,7 +54,6 @@ def _make_agent(*tool_names: str, max_iterations: int = 10, config: dict | None agent.client = MagicMock() agent._cached_system_prompt = "You are helpful." agent._use_prompt_caching = False - agent.tool_delay = 0 agent.compression_enabled = False agent.save_trajectories = False return agent diff --git a/tests/run_agent/test_tool_call_incremental_persistence.py b/tests/run_agent/test_tool_call_incremental_persistence.py index f9ceafec062..0cb4cfb916a 100644 --- a/tests/run_agent/test_tool_call_incremental_persistence.py +++ b/tests/run_agent/test_tool_call_incremental_persistence.py @@ -74,7 +74,6 @@ def _make_agent(): agent.client = MagicMock() agent._cached_system_prompt = "You are helpful." agent._use_prompt_caching = False - agent.tool_delay = 0 agent.compression_enabled = False agent.save_trajectories = False return agent diff --git a/tests/run_agent/test_turn_completion_explainer.py b/tests/run_agent/test_turn_completion_explainer.py index 386a74c754c..8f366924b06 100644 --- a/tests/run_agent/test_turn_completion_explainer.py +++ b/tests/run_agent/test_turn_completion_explainer.py @@ -52,7 +52,6 @@ def _make_agent(max_iterations: int = 10, config: dict | None = None) -> AIAgent agent.client = MagicMock() agent._cached_system_prompt = "You are helpful." agent._use_prompt_caching = False - agent.tool_delay = 0 agent.compression_enabled = False agent.save_trajectories = False # No fallback chain so empty responses exhaust deterministically.