Lesson 2.2: Designing tools an agent can use well
Learn the design rules that make tools easy for an agent: one job each, sharp descriptions, small results, safe writes with idempotency keys and the right size.
By the end of this lesson you will be able to judge a tool from the point of view of the model that must use it, rewrite a poor tool into a good one, keep tool results small, make write actions safe to repeat, and choose how finely to divide a system's abilities into tools. You will compare a tool that returns an 8,000-token record with one that returns 400 tokens and see that ten calls cost 80,000 tokens against 4,000, a factor of twenty. You will also count the duplicate refunds a retry can cause when a write is not protected: 30 a day, or ₹15,000, from a 3 per cent retry rate on 1,000 refunds. You should have read Lessons 2.1 and 1.4, which explain schemas, requests and the context window. A little Python helps for the final example. The mathematics is multiplication.
Lesson 2.1 showed the mechanics of a tool call. This lesson is about craft. Two agents with the same model and the same instructions can differ enormously in reliability, and the cause is often the tools. A model is a reader, and a tool is something it reads about, calls and reads the output of. If you design tools the way you would design an interface for a hurried, literal-minded colleague who cannot ask questions, the agent improves. If you merely expose your existing software, it usually does not.
Think of the model as a new colleague
Imagine hiring someone competent who knows nothing about your systems and who must work only from written notes. They cannot ask what a field means or look at the code. You give them a list of buttons, each with a label and a one-line description, and each press returns a page of text. How would you want the buttons designed?
You would want each button to do one thing, to be named for that thing, to say when it should and should not be pressed, to return exactly what is needed and not a data dump, and to fail with a clear message. You would not want a button whose effect depends on a hidden setting, or one that charges a customer when pressed twice by mistake. Those wishes translate into a set of design rules.
Rule 1: One tool, one job
A tool should do one clear thing that you can state in a sentence. "Look up a loan by number" is a good job. "Handle a loan" is not. A tool that does several things must make the model decide which, usually through an argument such as action: "lookup" or "update" or "close". This puts a hidden menu inside a single tool, and the description cannot be both short and complete. It also mixes reading with writing, which makes permissions hard to set: you cannot allow lookups and forbid closing if they are one tool.
Split mixed tools. getloan, updateloanaddress and closeloan are three clear jobs with three clear descriptions and three separate permissions.
Rule 2: Name and describe for the reader
The name should be a verb and an object, in the words the model will think in: searchinvoices, sendreply, getpaymenthistory. Avoid abbreviations and internal jargon such as lnmstrq or procx2. The description should answer four questions in a few lines:
What does the tool do?
When should it be used, and when should it not?
What are the arguments, and what does each one mean?
What does it return, and in what form?
Compare two descriptions of the same tool.
Weak · Strong"Searches invoices." · "Finds invoices matching a customer name, an invoice number or a date range. Use when you do not have the exact invoice number. Returns up to 10 matches, each with number, customer, amount and status, newest first. If more than 10 match, narrow the date range."
The strong version is about fifty words. It costs more tokens, but it prevents wrong calls, avoids wasteful follow-ups and tells the model what to do when there are too many results. Spend words in descriptions on what a new colleague would need and cannot guess.
Descriptions can include a short example of a typical call, which models follow closely. They should also name tools that are related but different: "Use getloan to look up one loan by number; use searchloans to find loans by customer." That sentence removes a common confusion.
Rule 3: Narrow, well-typed arguments
Every argument is a decision the model must get right. Reduce the decisions.
Prefer choices over free text. A status argument with the choices open, closed and overdue is safer than free text.
Prefer one identifier to a bundle of options. Ask for loannumber, not a customer name, a date and an amount from which to guess the loan.
Use defaults for rarely changed settings, such as limit = 10.
Name arguments for what they mean. duedatebefore is clearer than date2.
Give formats. "Date in YYYY-MM-DD form" avoids a dozen date styles.
Limit ranges. A page size of at most 50 stops a request for a million rows.
Each simplification removes a way to be wrong. A tool whose arguments can only be right or refused is easy to use.
Rule 4: Return what is needed, no more
This is the rule that teams most often break, and it has the largest effect on cost. The natural way to wrap a system is to return whatever its interface returns: a full customer record with sixty fields, an entire web page, a thousand rows. But everything a tool returns enters the context window and stays there for the rest of the run, being re-read at every later step.
Suppose a loan record from an internal system holds about 8,000 tokens of fields, notes and history, but the agent needs five facts from it: status, due date, outstanding amount, fee and the date of the last payment, about 400 tokens when labelled. If a run calls this tool ten times, the raw design adds 80,000 tokens to the context and the trimmed design adds 4,000, a factor of twenty. Because the history is re-read at each subsequent step, as Lesson 1.2 showed, the real difference in cost is larger still. At 2,000 runs a day, the raw design pushes 160 million tokens of results through the system, and the trimmed one 8 million. A raw design can also overflow the window outright, ending the run.
Predict first: a tool returns a 3,000-token record, and the agent needs only a 150-token summary of it. Would you rather the tool return the full record in case it is needed, or the summary with a way to ask for more? Decide before reading on.
Return the summary, and offer a way to ask for more. A good pattern is a two-level tool: a compact result by default, and an optional argument or a separate tool for details. getloan returns the five key facts, and getloandetails with section: "payments" returns the payment history only when asked. The model pays for details only when it needs them. If the summary leaves something out that was needed, the cost is one more call, which is much smaller than carrying the full record through every step.
Other ways to keep results small:
Select fields. Return the fields the task needs, labelled in plain words.
Limit rows, and say how many were left out: "10 of 143 matches shown, narrow the date range."
Summarise long text or return an excerpt with a position, such as the first 500 words and the total length.
Paginate. Return a page and a way to ask for the next.
Put big things aside. For a very large result, such as a file, save it and return a short reference and a description, with a tool to read parts of it.
Rule 5: Make results clear and consistent
A tool's output is read by the model, so write it for a reader. Use labels: status: overdue rather than a bare 2. Use a consistent shape from call to call, so the model learns it. Convert codes into words where the meaning is not obvious. Include the identifiers the model will need next, such as the loan number, so it need not guess. Express uncertainty plainly: "no payment found in the last 90 days" is better than an empty list, which could mean no payment or a failed search. Most important, a result should always say whether it succeeded: ok: true or an explicit error, never silence.
Rule 6: Make writes safe
A tool that only reads can be called again and again with no harm. A tool that changes something, such as sending a message, reversing a fee, creating a record or moving money, is another matter. An agent may call the same writing tool twice because it did not see the first confirmation, because a network timeout hid a success, or because the harness retried a call it thought had failed. The result of calling a payment tool twice is two payments.
The cure is idempotency: designing a write so that repeating it has the same effect as doing it once. The standard method is an idempotency key, a unique label for one intended action. The tool stores the keys it has already processed. If a request arrives with a key it has seen, it returns the earlier result and does nothing more.
Consider the cost. A company issues 1,000 refunds a day, each ₹500. Suppose 3 per cent of calls time out after the refund was in fact made, and the harness retries them. Without protection, the retry refunds again: 1,000 × 0.03 = 30 duplicate refunds a day, ₹15,000 a day, over ₹4.5 lakh a month. With an idempotency key built from the loan number, the fee and the date, the retry finds the key and returns the original confirmation. Duplicates fall to zero.
Other ways to make writes safe:
Preview then commit. A two-step pattern: preparerefund returns exactly what would happen, and confirmrefund carries it out. A person or a rule can sit between.
Limits in the tool, not the prompt. A refund tool that refuses amounts above ₹500 is safer than an instruction that says "do not refund more than ₹500". The model can forget an instruction. The tool cannot.
Narrow permissions. Give the tool only the access it needs. A tool that reverses fees need not be able to delete accounts.
Reversible by default. Prefer actions that can be undone, such as marking something for deletion rather than deleting it.
A record. Log every write with the key, the arguments and the outcome.
Rule 7: Choose the right granularity
How big should a tool be? There are two ways to go wrong.
Too fine. Mirroring every endpoint of an internal system gives the agent forty tools, each tiny. The model must chain many calls to do anything, which multiplies steps (and so the compounding errors of Lesson 1.2), fills the window with schemas and gives it many chances to pick wrongly.
Too coarse. A single handlecustomerrequest tool that does everything gives the model no control, hides what happened and cannot be given partial permissions.
The best level is usually the task level: tools shaped around what the agent is trying to do, not around how the underlying system is organised. To find out why an invoice was rejected, three internal calls (invoice, rule, history) might be wrapped into getinvoicerejectionreason, which returns a short answer. The agent makes one call where it would have made three. Notice that this moves the sequencing out of the model's hands and into code, which is more reliable for a fixed sequence. The rule of thumb: if a chain of calls is always the same, make it one tool; if the choice between them depends on what is found, leave them separate.
Before and after
Here is a poorly designed tool, followed by a better version.
· Poor · BetterName · proc · reverselatefee
Description · "Processes loan operation." · "Reverses one late fee on one loan. Use only after confirming the payment arrived on or before the due date. Refuses amounts above 500 rupees. Returns the confirmation and the new balance."
Arguments · op (text), id (text), val (any) · loannumber (4 to 8 digits), feeamount (number, 1 to 500), idempotencykey (text)
Result · The whole raw loan record, 8,000 tokens · ok: true, loannumber: 4471, feereversed: 500, newbalance: 11200
Failure · An empty response · ok: false, error: "feeamount exceeds the 500 rupee limit; send a person this case with escalate"
Safety · Runs again if repeated · Repeated key returns the earlier confirmation
The better version does one job, describes itself, constrains its arguments, returns little, fails clearly and is safe to repeat. None of this required a smarter model. It required care.
A small example in code
python
import json
DONE = {} # idempotency keys already processed
def trimloan(record):
"""Return only the facts the agent needs, labelled in plain words."""
return {
"loannumber": record["loannumber"],
"status": record["status"],
"duedate": record["duedate"],
"outstanding": record["outstanding"],
"fee": record["fee"],
"lastpaymentdate": record["payments"][-1]["date"] if record["payments"] else "none on record",
"note": f"{len(record['payments'])} payments on record; ask for details if needed.",
}
def reverselatefee(loannumber, feeamount, idempotencykey):
"""Reverse one late fee. Safe to repeat: the same key returns the first result."""
if idempotencykey in DONE:
return {DONE[idempotencykey], "repeat": True}
if not (1 <= feeamount <= 500):
return {"ok": False, "error": "feeamount must be between 1 and 500 rupees; use escalate for larger amounts."}
result = {"ok": True, "loannumber": loannumber, "feereversed": feeamount}
DONE[idempotencykey] = result
return result
raw = {"loannumber": "4471", "status": "active", "duedate": "2026-10-02", "outstanding": 11700, "fee": 500,
"payments": [{"date": "2026-09-01", "amount": 12000}, {"date": "2026-10-01", "amount": 12000}],
"internalnotes": "x" 30000, "audit": ["y" 5000] 3}
print(len(json.dumps(raw)) // 4, "tokens raw ->", len(json.dumps(trimloan(raw))) // 4, "tokens trimmed")
print(reverselatefee("4471", 500, "4471-2026-10-fee"))
print(reverselatefee("4471", 500, "4471-2026-10-fee")) # a retry: no second refund
print(reverselatefee("4471", 900, "4471-2026-11-fee")) # refused, with a way forward
The second call returns the first result marked as a repeat, and nothing is refunded twice. The third is refused by the tool itself, with a message that tells the model what to do next.
Common mistakes
Wrapping an existing interface unchanged. Shape tools around the agent's tasks, not around the system's structure.
Tools that do several things. Split them so descriptions are short and permissions are separate.
Returning everything. Large results fill the context and are re-read at every step. Return what is needed and offer more on request.
Unprotected writes. A retry or a repeated call can act twice. Use idempotency keys.
Limits only in the prompt. Put hard limits in the tool, where the model cannot forget them.
Vague or missing failure messages. Say what failed and what to do next.
Forty fine-grained tools. Merge fixed chains into task-level tools.
One giant tool. Keep enough separation for control and permissions.
Designing from the developer's side only. Test the tool by watching a model use it, and fix what confuses it.
Key takeaways
Design tools for a literal-minded reader who cannot ask questions: one job per tool, a verb-and-object name, and a description that says what, when, what for and what comes back.
Narrow arguments reduce mistakes: choices over free text, one identifier, defaults, formats and limits.
Results enter the context at every later step. A trimmed 400-token result in place of an 8,000-token record saves 76,000 tokens over ten calls, a factor of twenty.
Use two levels: a compact default and a way to ask for details. Paginate, select fields, and say how much was left out.
Make writes idempotent with a key, and put hard limits in the tool. In the example, 30 duplicate refunds a day (₹15,000) fall to zero.
Choose task-level granularity. Merge call chains that are always the same, and keep separate those whose order depends on findings.
Results should be labelled, consistent and always say whether they succeeded.
Check yourself
1. Why is a tool with an argument action that can be "lookup", "update" or "close" a poor design?
It hides several jobs in one tool, so its description is long, the model must choose a mode, and permissions cannot allow reading while forbidding closing. Split it into three tools.
2. A tool returns a 5,000-token result and could return 250 tokens. The agent calls it 8 times a run. How many tokens are saved per run?
(5,000 − 250) × 8 = 38,000 tokens.
3. A system makes 2,000 writes a day, 2 per cent are retried after having succeeded, and each is worth ₹300. What do unprotected duplicates cost per day?
2,000 × 0.02 = 40 duplicates, 40 × 300 = ₹12,000 a day.
4. Why put a refund limit in the tool and not only in the instructions?
The model may forget or misread an instruction, but a tool that refuses an amount above the limit enforces it every time.
5. Three internal lookups are always made in the same order to answer one question. How should they be exposed as tools?
As one task-level tool that does the sequence in code and returns a short answer, which is more reliable and cheaper than letting the model chain them.
Try this
Set aside sixty minutes, and use the tools you specified in Lesson 2.1.
1. For each tool, state its job in one sentence. If the sentence needs "and", split the tool.
2. Rewrite each description to answer what, when, arguments and what comes back. Add a "do not use for" line where tools are easily confused.
3. Tighten the arguments: add choices, formats, ranges and defaults. Remove any argument the model should not decide.
4. Write the result for each tool, trimmed to the fields the task needs, with labels. Estimate its size in tokens and compare with the raw interface.
5. Mark each write tool. Add an idempotency key, a hard limit and, where harm is high, a prepare-and-confirm pair.
6. Find any call chain that is always the same and merge it into one task-level tool.
7. Use the two simulators with your own numbers to find the context saved by trimming and the cost of unprotected retries.
A good result is a tool list in which every tool has one job, a description a new colleague could follow, tight arguments, a small labelled result and, for writes, protection against repeats. The next lesson looks at what happens after a tool runs: how to read results and errors, and how to retry wisely.
Tags: Agentic AI Tool design Idempotency Context management AI agents