Unused Tools is an AI code smell, a sign of trouble in LLM systems, in the Dispensables group. Tools in the schema that have not been called in months but take up context on every request.
Eight of the twelve tools have not been called in three months. Four never were. Still, all of them go out with every model call. Two of them (delete_temp_tables and run_python) have side effects: they delete data or run code.
Signs of Unused Tools:
Tools that have not shown up in traces for months stay on the list.
Nobody can say whether any of them is still needed, or who asked for it.
Old versions live next to new ones (_v1, _old, legacy_).
Side-effect tools stay available although no task needs them.
The definitions take a meaningful share of every call’s context.
flowchart LR
T[("1. 12 tools on the list")]:::store --> M(["2. Model reads them all on every call"]):::llm
M --> U["3. Always uses the same 4"]:::tool
T --> X["4. 8 unused, 2 with side effects"]:::untrusted
The list grew over time, one tool per request.
Every definition enters every call’s context, used or not.
Real usage concentrates on a few.
The rest only take up space and, when they have side effects, raise risk.
What a tool nobody calls weighs
A tool definition is text: name, description, and the JSON Schema of its parameters. The provider injects that text into the prompt on every request. A definition with a decent description and four or five parameters easily goes past a hundred tokens.
Run the math with example numbers. Suppose 200 tokens per definition and 8 unused tools: that is 1,600 extra input tokens per call. At 10,000 calls a day, you pay for 16 million tokens a day so the model can read tools it never picks. To get your real number, count the tokens of the tools array with your provider’s tokenizer.
Reasons
Tools come in and never leave. Adding one takes five minutes. Removing one requires finding out whether anyone uses it, and nobody has that data.
Plans that never happened.create_dashboard was built for a feature that never shipped. The feature died; the tool stayed.
Half-done migrations. The new version came in, the old one stayed “just in case”. Six months later, nobody remembers which is which.
No usage data. Without one span per tool call, the only source of truth is the team’s memory.
Generous demos. To impress in a presentation, the agent got tools that “might be useful”. In production, nobody asked for them.
Cost of ignoring
Wasted tokens on every call, as in the math above. The cost grows with volume while nothing new happens.
Wrong choices.legacy_query_v1 and query_warehouse have similar descriptions. One day the model picks the old one, which reads a stale table, and the answer comes out wrong with no error at all.
Attack surface. A side-effect tool nobody uses is a door nobody watches. If malicious text gets into the context, run_python is right there. The OWASP Top 10 for LLM Applications (2025 edition) calls this Excessive Agency, and “excessive functionality” is the first cause on its list.
Ghost maintenance. An API changes and breaks the forgotten tool. Nobody notices until the day the model decides to use it.
Always. Remove the ones that are never called, merge the ones that overlap, and keep removals reversible for a while. Targets: Tool Router and Progressive Disclosure.
Before removing anything, you need the count. If it does not exist, start with Add Tracing and let it run for a few weeks. With per-tool spans, the count is one query:
select tool_name, count(*) as calls, max(started_at) as last_callfrom spanswhere kind = 'tool_call' and started_at > now() - interval '90 days'group by tool_nameorder by calls desc
Remove in stages
Taking the tool out of the schema and deleting its code on the same day is the fastest way to learn, in production, that a rare flow depended on it. Do it in three stages:
Hide. Remove the definition from the list sent to the model, but keep the code. Run the agent’s eval before and after to confirm nothing got worse.
Watch. For a few weeks, log any attempt to call the removed name. Models sometimes “remember” tools mentioned in examples or in the history.
Delete. With no attempts and a stable eval, remove the code.
The rare-tool trap
Zero calls in 90 days does not prove a tool is useless. Some tasks follow a calendar: quarter close, annual tax filing, disaster recovery. Before deleting, ask the owner of the flow. If the tool is rare but needed, the answer is not to remove it but to load it only when the task calls for it, with Progressive Disclosure.
Payoff
Before
After
12 definitions on every call
4 always visible, rare ones on demand
Two versions of the same query
One tool per job
run_python available with no use
No side-effect tool without an owner
A list nobody reviews
A short list with measured usage
When to ignore
Rarely used but critical tools. A disaster-recovery tool may be called once a year. Keep it, but consider loading it only when needed.
A freshly launched agent. After two weeks of use, the count says little. Wait for data that covers at least one full business cycle.
A small list with no side effects. Three read-only tools, one of them rarely used, do not justify a refactoring. Write it down and revisit it with the next change.
Is it this smell?
Quick answers
What is Unused Tools?
Unused Tools is an AI code smell, a sign of trouble in LLM systems, in the Dispensables group. Tools in the schema that have not been called in months but take up context on every request.
How do you fix Unused Tools?
With Consolidate Tools. Each refactoring has safe step-by-step mechanics with an eval before and after.