refactor(agent): remove the inter-tool delay

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.
This commit is contained in:
Soju06 2026-07-14 05:30:59 +00:00 committed by Teknium
parent 1a088989bc
commit ce9f6712ff
13 changed files with 47 additions and 24 deletions

View file

@ -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

View file

@ -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:

View file

@ -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,

View file

@ -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

View file

@ -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

View file

@ -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"}

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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.