Module 3 · Agent Tools, Knowledge, Memory & Research Design · scripted
Errors That Teach
Overview
Every tool call can fail — and computing has fifty years of tradition about what to say when it does: terse codes, written for a human reading a log file later. But an error, like every result, is a message into a conversation — and for the first time in computing history, the reader at the moment of failure can actually read. This lesson rewrites errors for that reader: what happened, why, and what to try next — so a failure becomes the most instructive message on the wall, and the conversation gets better after things go wrong.
Who were error messages written for?
Every tool call can fail. And computing has fifty years of tradition about what to say when it does:
The classic error
ERROR 422: INVALID_PARAMETER E_PRECONDITION_FAILED status=403 message="Forbidden"
Most errors are terse records of a problem in the code. They are not instructive: someone has to look at the error, look at the code, and start figuring it out.
That design was correct for its world: the program that hit the error had no intelligence and could only crash or retry blindly, so the explanation waited for a human. But the receiver of a tool-call error is intelligent — so we can make errors more useful.
The receiver changed
An error returning into an agent's conversation lands somewhere no error has landed before: in front of an intelligent reader, mid-task, holding the full context of what it was trying to do — and occupying the user seat, where words steer. This reader can recover. But only if the error tells it how. Watch the same failure, written for each reader:
You have the following tools available for use:
- book_room( room, date, start, end )
Task: Book the department seminar room for Thursday 14:00–16:00.
book_room(room = "seminar", date = "2026-09-17", start = "14:00",
end = "16:00")ERROR 409: CONFLICTbook_room(room = "seminar", date = "2026-09-17", start = "14:00",
end = "16:00")ERROR 409: CONFLICTThe booking system is returning an error I can't interpret. The room may be taken, or the request may be malformed — I can't tell which. I'll report back that the booking failed.
book_room(room = "seminar", date = "2026-09-17", start = "14:00",
end = "16:00")CONFLICT: seminar room is taken 13:00–15:00 on 2026-09-17
(research group meeting). Free 15:00 onward. Room "aud-2" (cap. 30)
is free 14:00–16:00. To book it:
book_room(room="aud-2", date="2026-09-17", start="14:00",
end="16:00").book_room(room = "aud-2", date = "2026-09-17", start = "14:00",
end = "16:00")Booked: aud-2, 2026-09-17 14:00–16:00.The seminar room was taken until 15:00, so I've booked auditorium 2 — same time, seats 30. If you'd rather keep the seminar room, it's free from 15:00.
The recovery contract
What did the second error contain that the first didn't? Three things — and they are the whole design:
The recovery contract
Every error is a turn in the conversation. Make it say: WHAT happened — precisely, in the terms the agent is working in WHY — the rule or state of the world that caused it NEXT — the best move from here, as concretely as code can make it An error with WHAT alone is a report. With WHY, it's an explanation. With NEXT, it's a recovery.
Notice this is our closure principle, extended to the path we forgot: every successful result should hand the agent its next move — and every failure should too. A tool set with closure only on the happy path is a language where any stumble is a dead end.
Precompute the recovery
Here is the part almost everyone underuses. At the moment of failure, your code knows things — the valid values, the free time slots, the current state — with a certainty the model can never match. Don't describe the recovery. Compute it.
You have the following tools available for use:
- log_expense( amount, currency, date, category )
Task: Log this expense: conference dinner, 940 kr, yesterday.
log_expense(amount = 940, currency = "kr", date = "yesterday",
category = "food")2 problems, both fixable:
· currency "kr" — this system uses ISO codes; you mean "NOK".
· date "yesterday" — must be absolute; today is 2026-09-18, so
yesterday = "2026-09-17".
· category "food" is valid.
The corrected call:
log_expense(amount=940, currency="NOK", date="2026-09-17",
category="food")log_expense(amount = 940, currency = "NOK", date = "2026-09-17",
category = "food")A map from failures to recoveries
Failures are not all one kind, and each kind has a natural NEXT. This table is most of tool-error design:
What failed → what the error should teach
Two rows deserve a closer look. The precondition row includes a pattern that connects to the library from earlier: an error can require the reading — "first use of this instrument: read its manual, then retry." Self-training on demand, enforced at the gate. And the not permitted row is richer than it looks:
You have the following tools available for use:
- log_expense( … )
- request_exception( expense_id, justification, approver )
Task: Log the client dinner from the Oslo trip — 2,340 kr, four people.
log_expense(amount = 2340, currency = "NOK", date = "2026-09-15",
category = "client-meals")DENIED: client-meals limit is 500 kr per person; 4 attendees →
max 2000 kr. This is not final. Exceptions are granted for
client-facing events with a business justification. To request one:
1. the expense is parked as draft-8812;
2. call request_exception(expense_id="draft-8812",
justification=<why this dinner served a business purpose>,
approver="dept-head") — approvals usually within 2 days.request_exception(expense_id = "draft-8812", justification =
"Dinner with 3 representatives of the client following contract
negotiation; continuation of the working session.",
approver = "dept-head")Exception request filed; awaiting dept-head approval.The dinner exceeds the per-person meal limit, so I've filed an exception request with the business justification; the department head typically responds within two days. The expense is parked as a draft until then.
Fix the instance, or teach the rule?
One economic subtlety. Compare two errors for the same slip:
Two prices for the same lesson
The first repairs one call. The second conditions the whole trajectory: the agent won't make the mistake again in THIS conversation — the rule is now on the wall, part of every future turn. Teach the rule when the agent will act again under it; fix the instance when it won't.
Remember the boundary, though: the model's weights are frozen. The rule is learned by the conversation, not by the model — the next conversation starts ignorant again, and its first slip buys the same lesson. That is not a flaw; it is the pricing. The rule costs nothing until a trajectory needs it, once per trajectory that does.
- Find WHAT, WHY, NEXT in Example 4's error. Which sentence is which — and which could code alone have written?
- In Example 1, whose failure is it — the agent's or the designer's? Defend your answer.
- What is the most common error your own tools would return — and what does its NEXT look like, precomputed?
Take three real error messages from any system you use — an API, a statistics package, a university form. For each, rewrite it as the error an agent should receive: WHAT, WHY, NEXT, with everything code could know precomputed into the text. Then trace one recovery: show the failing call, your error, and the agent's next call. Which of your three rewrites teaches a rule, and which merely fixes an instance?