Skip to content

Commit 02a4300

Browse files
committed
[bugf][agent][tools=[] enables the deferred-tool machinery it has nothing to search]
exists() is 'is not None', so exists([]) is True and an empty tools list counted as having tools. An agent built with tools=[] was given the tool_search schema and DYNAMIC_TOOLS_NOTICE in its system prompt, then advertised a tool whose catalog is empty on every request. tools=None produced the correct empty result, so the two spellings of 'no tools' behaved differently. CLAUDE.md documented the difference as a footgun to avoid; the warning goes away with the cause. Truthiness at the two call sites. mcp_enabled and max_loops='auto' still enable deferral on their own, so an autonomous agent with no tools keeps the loader it needs.
1 parent 8b7b80c commit 02a4300

3 files changed

Lines changed: 49 additions & 4 deletions

File tree

CLAUDE.md

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1086,8 +1086,6 @@ from swarms import Agent
10861086

10871087
**Don't instantiate heavyweight structures inside tight loops** — create agents and workflows once, reuse them across calls.
10881088

1089-
**Don't pass `tools=[]` (empty list)** — pass `tools=None` instead. An empty list can confuse schema generation.
1090-
10911089
**Don't use `streaming_on=True` and `streaming_callback` together on the same agent**`streaming_on` streams to stdout; `streaming_callback` streams to your function. Pick one.
10921090

10931091
**Don't set `context_compression=False` on very long autonomous sessions** — without compression the agent will eventually hit the context limit and raise an error.

swarms/structs/agent.py

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -676,8 +676,11 @@ def __init__(
676676
self.system_prompt += "\n\n" + handoff_prompt
677677

678678
# One condition so the notice cannot diverge from the loader.
679+
# Truthiness, not exists(): exists() is `is not None`, so tools=[]
680+
# counted as having tools and bought an empty agent the tool_search
681+
# schema plus the notice in every prompt.
679682
defers_tools = self.dynamic_tools and (
680-
exists(self.tools)
683+
bool(self.tools)
681684
or self.mcp_enabled
682685
or self.max_loops == "auto"
683686
)
@@ -686,7 +689,7 @@ def __init__(
686689
if defers_tools:
687690
self.system_prompt += DYNAMIC_TOOLS_NOTICE
688691
self.setup_dynamic_tools()
689-
elif exists(self.tools):
692+
elif self.tools:
690693
self.tool_handling()
691694

692695
if self.llm is None:

tests/structs/test_agent.py

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3311,6 +3311,50 @@ def test_mismatched_lengths_raise_instead_of_dropping_tasks(self):
33113311
)
33123312

33133313

3314+
class TestEmptyToolsList:
3315+
"""`tools=[]` must mean the same as `tools=None`.
3316+
3317+
`exists()` is `is not None`, so an empty list counted as having tools:
3318+
the agent was given the `tool_search` schema it had nothing to search and
3319+
DYNAMIC_TOOLS_NOTICE in every prompt.
3320+
"""
3321+
3322+
@staticmethod
3323+
def _agent(**kwargs):
3324+
return Agent(
3325+
agent_name="empty_tools_agent",
3326+
model_name="gpt-4o-mini",
3327+
max_loops=1,
3328+
**kwargs,
3329+
)
3330+
3331+
def test_empty_list_advertises_no_tools(self):
3332+
assert self._agent(tools=[]).tools_list_dictionary == []
3333+
3334+
def test_empty_list_matches_none(self):
3335+
empty = self._agent(tools=[])
3336+
none = self._agent(tools=None)
3337+
assert (
3338+
empty.tools_list_dictionary == none.tools_list_dictionary
3339+
)
3340+
assert ("tool_search" in empty.system_prompt) == (
3341+
"tool_search" in none.system_prompt
3342+
)
3343+
3344+
def test_a_real_tool_still_defers(self):
3345+
def sample(x: str) -> str:
3346+
"""Return x.
3347+
3348+
Args:
3349+
x: anything
3350+
"""
3351+
return x
3352+
3353+
agent = self._agent(tools=[sample])
3354+
assert agent.tools_list_dictionary
3355+
assert "tool_search" in agent.system_prompt
3356+
3357+
33143358
if __name__ == "__main__":
33153359
# Run all tests
33163360
results = run_all_tests()

0 commit comments

Comments
 (0)