# Your agent doesn't need 40 tools

Balakrishna · 2026-09-22

Early this year we connected a Uniwork agent to, more or less, everything. CRM, invoices, projects, calendar, email, file storage. Each endpoint became a tool. We ended up with 41 of them.

It worked in the demo. Then real users started talking to it.

## What went wrong

Three things, in order of how long they took us to notice.

**It picked the wrong tool.** We had `get_invoice`, `find_invoice`, `search_invoices` and `list_invoices_for_customer`. They made sense to the engineers who wrote them. To the model they were four doors into the same room. It guessed, and it guessed differently each time.

**Every request was heavy.** Tool definitions are part of the prompt. Forty-one of them, with descriptions and JSON schemas, added thousands of tokens to every single turn, even when the user only said "thanks".

**Errors were useless.** When a tool failed it returned whatever the API returned. Often that was `{"error": "bad request"}`. The model would try again with the same input, three times, and then apologise.

## What we do now

### Design tools around jobs, not endpoints

The user doesn't want to "call GET /invoices". They want to know if a customer has paid. So now there is one tool, `find_invoices`, with optional filters for customer, status and date. Behind it, our code calls whichever endpoints it needs.

We went from 41 tools to 11. Accuracy went up. Cost went down. Nothing else changed.

### Write descriptions for a new colleague

A good test: if you handed the tool description to someone on their first day, could they use it correctly? "Searches invoices" fails that test. "Finds invoices by customer name, status (paid, unpaid, overdue) or date range. Use this before creating a new invoice to avoid duplicates." passes.

### Make errors helpful

When a tool fails we return a sentence the model can act on: "No customer called 'Acme Ltd'. Close matches: 'ACME Pvt Ltd', 'Acme Foods'." The retry loops mostly disappeared.

### Load rarely-used tools only when needed

Some tools are needed once a week. Those don't sit in the prompt all the time. Claude's API has a tool search feature where you mark tools as deferred and the model finds them when it needs them. MCP clients increasingly do something similar. Either way, the model only pays for what it uses.

### Put approvals in code, not in the prompt

"Never send an email without asking" belongs in the prompt, sure. But it also belongs in code. Any tool that sends, books or pays goes through an approval step that the model cannot skip, however politely it's asked. EQO works this way and we'd never ship an agent that didn't.

## A quick checklist

Before adding a tool, we now ask:

- Could an existing tool do this with one more parameter?
- Is the name obviously different from every other tool's name?
- Does the description say *when* to use it, not just what it does?
- What does the model see when it fails?
- Does it change anything in the real world? Then where's the approval?

Fewer, better tools. It's the least exciting advice I can give and the one that has made the biggest difference for us.
