[BUG][AutonomousAgentLoop][tool_search returns irrelevant tools and the subtask stalls instead of continuing]
Summary
With max_loops="auto", the autonomous loop stalls on its first subtask. dynamic_tools defaults to True (swarms/structs/agent.py:399), so this needs no opt-in. The agent calls tool_search, gets back tools that have nothing to do with what it asked for, and then tells the user it is blocked "because tool loading only takes effect on the next turn" — instead of continuing the subtask with the next request, which is exactly when the loaded schemas do become available.
Reproduction
from dotenv import load_dotenv
from swarms import Agent
load_dotenv()
agent = Agent(
agent_name="Quantitative-Trading-Agent",
agent_description="Advanced quantitative trading and algorithmic analysis agent",
system_prompt="You are Quantitative-Trading-Agent ...",
model_name="gpt-5.4",
max_loops="auto",
persistent_memory=False,
)
agent.run(
task=(
"Analyze the best semiconductor ETFs and provide a detailed comparison. "
"Include metrics such as performance, expense ratio, holdings, and any "
"notable strategies."
)
)Observed
Planning succeeds — a 7-step plan is produced. Execution then reaches the first subtask and:
Agent: Quantitative-Trading-Agent Function Call: tool_search
query: web search browser fetch webpage financial market data ETF fund
profile holdings performance quote screener table extraction
max_results: 10
Message to User [WARNING]
I'm blocked from completing the subtask in this same turn because tool
loading only takes effect on the next turn, and no web/data retrieval
tools were available immediately. I have loaded the availab...The subtask does not proceed.
What is actually happening
Two separate problems compound.
1. tool_search answers an unsatisfiable query with irrelevant tools.
There are no web/market-data tools in the catalog at all, but the search does not say so — it returns its two best lexical matches. Reproduced offline, no model involved, by doing what AutonomousAgentLoop._run_autonomous_loop does at swarms/agents/autonomous_loop.py:314-320:
from unittest.mock import patch
from swarms.structs.agent import Agent
from swarms.agents.autonomous_loop import ALWAYS_LOADED_TOOLS
from swarms.structs.autonomous_loop_utils import get_autonomous_planning_tools
with patch("swarms.structs.agent.LiteLLM"):
a = Agent(agent_name="Q", max_loops="auto", dynamic_tools=True)
pt = get_autonomous_planning_tools()
control = [t for t in pt if t["function"]["name"] in ALWAYS_LOADED_TOOLS]
a.setup_dynamic_tools(always_loaded=control)
a.defer_tool_schemas([t for t in pt if t not in control])
print(a.tool_loader.run_search(
query="web search browser fetch webpage financial market data ETF holdings quote",
max_results=10,
))glob: Find files by name pattern, newest first. ...
grep: Search for a pattern in files. ...
Loaded 2: glob, grep. They are callable from your next turn.The full deferred catalog for an auto agent with no user tools is:
assign_task, cancel_sub_agent_tasks, check_sub_agent_status, create_file,
create_sub_agent, delete_file, glob, grep, list_directory, read_file,
run_bash, update_fileNothing there can fetch an ETF quote. A query that matches nothing relevant should report a miss, not hand back glob and grep as though they answer it.
2. The "next turn" wording reads as "you are blocked".
DYNAMIC_TOOLS_NOTICE (swarms/tools/dynamic_tool_loader.py:92) says:
- The loaded tools become callable on your NEXT turn.
and every successful search result ends with They are callable from your next turn. The model in the transcript quoted this back almost verbatim as its reason for stopping and warning the user.
Mechanically the tools are ready immediately: Agent._tool_search_tool rebuilds the LLM in place after loading (swarms/structs/agent.py:2150-2152, "Loading changes the tool list, so the LLM is rebuilt here"), so the very next request in the same subtask carries the new schemas. The instruction is technically true and pragmatically misread — "next turn" is the next request, not a reason to hand control back to the user.
The notice already tries to guard against this ("Never say a task cannot be done ..."), but respond_to_user is in ALWAYS_LOADED_TOOLS (swarms/agents/autonomous_loop.py:82-91), so surrendering the turn is always one call away.
Expected
- A
tool_searchthat matches nothing useful says so plainly, rather than loading the highest-scoring unrelated tools. - After a
tool_searchinside a subtask, the loop continues that subtask on the next request instead of the agent treating the load as a blocking event. - Ideally the loop notices a subtask that requires a capability the catalog does not contain, and fails or replans rather than burning iterations.
Possible directions
- Apply a minimum relevance floor in
run_searchso a query with no real match returns a miss plus the catalog listing (the miss path already exists — it is just not reached when weak matches score above zero). - Reword
DYNAMIC_TOOLS_NOTICEstep 2 and the search result footer: "these are callable from your next tool call — continue the subtask now", rather than "your NEXT turn". - Have the execution loop treat a turn whose only tool call was
tool_searchas a non-iteration, so loading never costs the subtask its budget.
Environment
- swarms
master@a4366d8d1 - Python 3.12, macOS
model_name="gpt-5.4",max_loops="auto", no user-supplied tools,dynamic_toolsleft at its default ofTrue
Source: kyegomez/swarms