Module 2 · Tools, Knowledge, Memory & Research Design · scripted
Errors That Teach
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, and its intended reader
ERROR 422: INVALID_PARAMETER E_PRECONDITION_FAILED status=403 message="Forbidden"
Written for a human — reading a log file, hours or days later, reconstructing what went wrong. A code to look up. A string to search for. The receiver of the error at the moment it happened — the program — had no intelligence. It could not read, could not adapt, could not recover. So no one wrote errors for it.
The classic error is a coroner's report.
That design was correct for its world. The program that hit the error could only crash or retry blindly; the only reader who could act on an explanation arrived after the fact. Terse codes for the machine, searchable strings for the post-mortem human — that is what errors were for.
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:
Book the department seminar room for Thursday 14:00–16:00.
Tools: book_room(room, date, start, end).
⟨tool_use⟩
book_room(room = "seminar", date = "2026-09-17", start = "14:00",
end = "16:00")⟨tool_result⟩
ERROR 409: CONFLICT⟨tool_use⟩
book_room(room = "seminar", date = "2026-09-17", start = "14:00",
end = "16:00")⟨tool_result⟩
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.
⟨tool_use⟩
book_room(room = "seminar", date = "2026-09-17", start = "14:00",
end = "16:00")⟨tool_result⟩
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").⟨tool_use⟩
book_room(room = "aud-2", date = "2026-09-17", start = "14:00",
end = "16:00")⟨tool_result⟩
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.
Log this expense: conference dinner, 940 kr, yesterday.
Tools: log_expense(amount, currency, date, category).
⟨tool_use⟩
log_expense(amount = 940, currency = "kr", date = "yesterday",
category = "food")⟨tool_result⟩
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")⟨tool_use⟩
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:
Log the client dinner from the Oslo trip — 2,340 kr, four people.
Tools: log_expense(…), request_exception(expense_id, justification, approver).
⟨tool_use⟩
log_expense(amount = 2340, currency = "NOK", date = "2026-09-15",
category = "client-meals")⟨tool_result⟩
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.⟨tool_use⟩
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")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?