Skip to main content
Tool definitions are part of the prompt. They are re-sent on every request and they sit inside the cached prefix, so a large tool set is paid for continuously. Dynamic tool loading keeps your tools registered and executable but absent from the schema list sent to the model, exposing a single tool_search tool that loads them on demand.
The 16 built-in autonomous-loop tools alone are roughly 2,600 tokens per request, and a single MCP server can add 40 more tools on top. Selection accuracy also falls as the list grows — a model choosing among 80 tools chooses worse than one choosing among 8.

How It Works

Tools are deferred: registered, searchable, and executable, but not advertised. Only tool_search is always present alongside your control tools. The model searches the catalog by keyword, the matching schemas are loaded, and they become callable on the next turn.
A deferred tool costs one extra round trip. With max_loops=1 the model can search but never call what it found. Use max_loops=2 or higher for a single tool call, or max_loops="auto".

Enabling It

dynamic_tools is a constructor parameter on Agent and is True by default.
bool
default:"True"
Defer tool schemas behind tool_search instead of sending them all on every request. Set to False to restore classic eager registration, where every tool schema ships with every call.

When Deferral Actually Activates

Setting dynamic_tools=True on its own does nothing. Deferral needs something to defer, so it activates only when at least one of these is true: If none apply, agent.tool_loader stays None and no catalog is built.
When deferral activates, a system-prompt notice headed ## MOST TOOLS ARE NOT LOADED is appended once at construction. It tells the model that its visible tool list describes what exists, not what it can call right now, and that it must search before concluding a task is impossible.

Inspecting the Catalog

agent.tool_loader is a DynamicToolLoader. It reports what is deferred and what has been loaded so far.
After a search, the loaded tools move across:

Searching the Catalog

The model calls tool_search with a query. Matching is deliberately simple, dependency-free, and deterministic, so it can be tested.
The query is lowercased and split into tokens, with underscores treated as spaces so get_weather matches both get and weather. Each catalog entry scores 3 points for a name match and 1 point for a description or parameter-name match, summed over the query’s terms. Entries scoring zero are dropped, and results are sorted by score then name.
Prefix the query with select: to load tools by exact name, bypassing ranking, limit, and min_score_ratio entirely.
Unknown names are silently ignored. If none of the names match, the loader falls back to a keyword search over the guessed names rather than returning nothing.
Common words (a, the, and, of, to, can, please, …) and single characters are dropped before matching. Without this, a query like "weather in a city" would match every tool whose description contains "a" and load the entire catalog, defeating the point.
A search that matches nothing returns the available tool names rather than an empty string, so the model can retry with select:. The listing is capped at 30 names with a (+N more) suffix so a large catalog cannot flood the conversation.

Dynamic Tools in the Autonomous Loop

With max_loops="auto", the loop’s own tools are deferred too — but the control tools that let the agent make progress are never deferred, since an agent that has to search for its own complete_task cannot finish. Always loaded: create_plan, think, subtask_done, complete_task, respond_to_user. Deferred into the catalog: create_file, update_file, read_file, list_directory, delete_file, run_bash, grep, create_sub_agent, assign_task, check_sub_agent_status, cancel_sub_agent_tasks, plus every tool you passed in tools.

Plan-Based Pre-Warming

Searching one subtask at a time wastes turns. When the agent calls create_plan, the loop takes the task description plus every step description as a single query and pre-loads the tools that plan implies — at no extra turn cost, since it runs inside the create_plan handler that just succeeded. The create_plan result then tells the model what it already has:
The score ratio matters here: a long plan description contains enough common words to give weak matches a nonzero score, so speculative pre-warming filters harder than an explicit search does.
selected_tools filters the loop’s tool list before deferral, so a tool you exclude is not merely hidden — it never enters the catalog and cannot be found by tool_search at all.

MCP Servers

MCP tools are the strongest case for deferral: a single server can contribute dozens of schemas that would otherwise ship on every request. With dynamic_tools=True they join the catalog instead.
MCP deferral is lazy — the server is contacted while the LLM is being built, not at construction. To inspect the catalog before running, build the LLM yourself:
The fetch happens once per agent and is cached, so rebuilding the LLM does not re-contact the server. If the server is unreachable, the agent still builds: the failure is logged, zero tools are deferred, and tool_search simply has nothing to find.
MCP entries are registered as schemas with no local callable, so they never appear in loader.handlers(). They are dispatched through the MCP manager instead. This is by design — do not treat an empty handlers() as a sign that MCP tools failed to load.

Prompt Caching

Every load changes the tool array, which invalidates the provider’s cached prompt prefix. Two mitigations are built in:
  1. schemas() returns tools in a stable order — always_loaded first, then tool_search, then loaded tools sorted by name — so two runs that load the same tools produce an identical prefix.
  2. The tool_search description explicitly instructs the model to load everything it expects to need in a single call rather than one tool at a time.
If you use prompt_caching=True, prefer select: with a full list of names, or lean on plan-based pre-warming, so the tool array settles early and stays put for the rest of the run.

Turning It Off

Set dynamic_tools=False for classic eager registration — every schema ships with every request, and agent.tool_loader is None.
Prefer eager registration when:

Gotchas

tool_struct is built from self.tools before deferral, so a model that guesses a correct tool name can still execute it. Only the schema is withheld. Dynamic tool loading is a token-and-accuracy optimization, not an access control mechanism.
A tool of your own named tool_search is dropped from the catalog with a warning, and becomes permanently uncallable — both it and the search tool would appear in the list and the model could not tell them apart. Rename yours.
Once deferral is active, tools_list_dictionary becomes an output of the loader — it is overwritten every time a search loads something. Schemas you append to it after construction are clobbered. Register them with agent.defer_tool_schemas([...]) instead.
Calling setup_dynamic_tools() discards the existing loader, including which tools were already marked loaded. Autonomous agents call it twice by design. MCP schemas survive via an internal cache; anything you registered manually must be re-added with defer_tool_schemas().
The handoff_task tool registered by the handoffs parameter is preserved across setup and stays always-loaded, so delegation works on turn one without a search.

Next Steps

Dynamic Tool Examples

Runnable end-to-end examples for local tools, MCP, and the autonomous loop

DynamicToolLoader API

Full class reference for the loader, its search algorithm, and its methods

Agent Tools

How tools are defined, converted to schemas, and executed

MCP Integration

Connect MCP servers and defer their tool catalogs

Reference

  • Loader: swarms/tools/dynamic_tool_loader.py
  • Agent parameter and activation: swarms/structs/agent.py (dynamic_tools, setup_dynamic_tools, defer_tool_schemas, defer_mcp_tools)
  • Autonomous-loop control tools and pre-warming: swarms/agents/autonomous_loop.py