Enterprise DNA
Chapters and contents

Chapter 4

Give It Tools That Are Safe and Clear

Create tool contracts that let the agent act without creating chaos.

32 min read intermediate v1.0.0 Updated 23 July 2026

The real business problem

A model on its own cannot do anything in your business. It can write a great email, but it cannot send it. It can describe the perfect invoice, but it cannot post it to your accounting system. It can summarise a contract, but only if someone hands it the contract first.

Tools are how the agent reaches out of the chat and into the real world. And this is exactly where most agent projects turn dangerous. The moment you give a model a button that sends money, deletes records or emails a customer, a wrong guess stops being a funny screenshot and starts costing you.

So the skill in this chapter is not “connect the agent to your systems.” Anyone can do that. The skill is connecting it so the agent can act usefully without creating chaos when it gets something wrong. After this chapter you will be able to write a tool contract that is clear enough for the model to use correctly and safe enough that a bad call cannot do real damage.

The core idea

A tool is a function you hand the agent, described in plain language, that it can choose to call. The model does not run your code. It reads your description of the tool, decides it is the right one, fills in the inputs and asks the system to run it. The system runs it and hands the result back.

That means the model is reading a contract, not your source code. If the contract is vague, the agent guesses. If the contract is clear, the agent behaves. Everything good about tools comes from writing that contract well, and everything bad comes from leaving it loose.

A good tool contract has six parts.

  • A clear name that says what it does. get_invoice is good. helper2 is not.
  • A plain description of when to use it and when not to.
  • Typed inputs so the model cannot pass a date where a number belongs.
  • Safe defaults so a missing field does the harmless thing, not the expensive one.
  • A predictable output the model can read the same way every time.
  • Useful errors that tell the agent what went wrong and what to try next.

Get those six right and the agent uses the tool the way a careful person would.

A simple model

The split that matters most is read tools versus write tools.

A read tool looks at the world. It pulls a document, fetches a balance, lists open tickets. If it runs twice, nothing bad happens. You can be generous with read tools.

A write tool changes the world. It sends an email, posts an invoice, updates a record, charges a card. If it runs twice, you have a problem. Write tools need a tighter contract, narrower permissions and a way to survive being retried.

Read tool (safe to repeat) Write tool (handle with care)


agent asks -> get_document agent asks -> post_report system reads the doc system checks the idempotency key returns the text if new: makes the change run twice -> same result if seen: returns existing result no harm done run twice -> one report, not two

Read tools are safe to repeat. Write tools must be built to survive a retry.

A concrete example

Take a common job. Every Monday, someone on your team opens last week’s activity document for a client, reads it, and posts a short status report into your project tool. It is not hard. It is just repetitive and easy to skip.

Two tools do it. get_document pulls the client’s activity doc by id and returns its text. post_report takes a client id, a title and a body, and posts the report. The agent reads the document, decides what matters, writes the summary and posts it.

get_document is a read tool. If it runs twice, you have read the same doc twice. No harm. post_report is a write tool. If it runs twice, the client now has two identical reports and someone has to go and clean that up. Same agent, same task, but the two tools need very different care.

The implementation pattern

Here is what a clean write-tool contract looks like. The shape matters more than the language.

{
  "name": "post_report",
  "description": "Post a status report to a client's project. Use only after get_document. Do not call twice for the same week.",
  "input_schema": {
    "type": "object",
    "properties": {
      "client_id": { "type": "string", "description": "The client's id from the CRM. Required." },
      "title":     { "type": "string" },
      "body":      { "type": "string", "description": "Plain text, 80 to 200 words." },
      "week_of":   { "type": "string", "description": "Monday of the week, as YYYY-MM-DD. Used to prevent duplicates." },
      "draft":     { "type": "boolean", "description": "If true, save without publishing. Defaults to true." }
    },
    "required": ["client_id", "body", "week_of"]
  }
}

Three things are doing the safety work here. The description tells the model exactly when to use the tool and warns it off double posting. The inputs are typed, so week_of cannot arrive as a number. And draft defaults to true, so the harmless outcome is the default. If the agent forgets to set it, you get a draft a human can check, not a report already sent.

Now handle the retry. Agents retry. The network drops, a run restarts, the workflow fires twice. If post_report inserts a row every time it is called, retries create duplicates. The fix is idempotency. Give each write a key that describes the exact action, here the client and the week, and make the tool refuse to do the same thing twice.

key = "report:" + client_id + ":" + week_of

if key already recorded:
    return { status: "already_done", report_id: existing_id }
else:
    create the report
    record the key
    return { status: "created", report_id: new_id }

Now a retry is safe. The second call sees the work is done and returns the existing result instead of making a new one. Idempotency is the single most important habit for any tool that spends money or contacts a customer.

The last piece is errors. When a tool fails, do not throw a raw stack trace at the model. Tell it what happened in words it can act on.

{ "status": "error", "code": "client_not_found",
  "message": "No client with id C-4821. Check the id or call list_clients first." }

With that, the agent can recover. A raw “500 Internal Server Error” gives it nothing, so it either gives up or guesses. A clear error tells it the next move.

What breaks

Least privilege fixes most of this. A tool should do exactly one job and nothing more. Not “query the database” but “get this client’s last invoice.” Not “send email” but “send the weekly report to this client’s saved address.” You are not trying to make the agent weak. You are making sure that when it is wrong, and it will sometimes be wrong, the blast radius is one client and one week, not your whole customer list.

Decision rules

The Document to Report Agent lab builds the exact two-tool pattern from this chapter. You write get_document and post_report, give the write tool an idempotency key, then trigger a retry on purpose and watch the tool do the safe thing instead of posting a second copy. It is the smallest complete example of a read tool and a write tool working together.

Related lab Document to Report Agent

Practical checklist

Before you move on

What changes after this chapter

You stop thinking of tools as “connect the agent to my systems” and start thinking of them as contracts. A read tool can be generous. A write tool earns trust with typed inputs, safe defaults, idempotency and clear errors. That distinction is what lets an agent act in a real business without keeping you up at night.

Next: Chapter 5, The Workflow Control Loop, where these tools get run inside a loop that manages state, retries and stopping.

Suggest an edit Report an issue