Context EngineeringLesson 63 of 87

Contents

Context Engineering

Lesson 63 of 87. Sign in to keep your place.

Module 7 · Tools, schemas and the agent’s window

An error message is context too

8 min read

The cheapest teaching signal you have

When a tool fails, whatever string you return goes into the window and becomes the model’s entire understanding of what happened. It is a prompt written at the worst possible moment, usually by whoever wrote the exception handler two years ago for a different audience.

Here is that audience mismatch in one line:

Text
Error: HTTP 500

A human on call reads that, opens the logs, and finds the cause in a minute. A model has no logs. It has five characters of number. It will do the only thing available, which is guess, and the most common guess is that this is transient, so it calls again. And again.

Now the arithmetic. Each retry is a full request carrying the whole accumulated context. Eight retries on a 40,000-token conversation is 320,000 input tokens spent producing nothing, plus eight round trips of latency the user is sitting through.

Three things an error must carry

What failed, specifically. Not “invalid input” but which argument and why. customer_id "c_99" is malformed: expected format c_NNNN with exactly four digits.

Whether a retry can help. This is the one that saves the loop, and almost no error message says it. A model cannot distinguish a rate limit from a not-found from a malformed argument unless you tell it, and the correct response to each is completely different. Say it in words: Retrying with the same arguments will fail. Or: Transient; retry once after a short wait.

What to do instead. The model needs a legal next move. Without one it either gives up or invents something. Use search_orders to find the id first.

Put together:

Text
get_order failed: no order with id ORD-88213 exists.
This is not transient — retrying with the same id will fail identically.
If the customer gave this number verbally it may be mistyped; use
search_orders with their customer_id and a date range to find the real id.

Around 60 tokens. It replaces a flail of six or eight calls with one correct one. There is no cheaper intervention anywhere in an agent system.

Write the error once, in a wrapper

This does not belong scattered through tool implementations. Put it at the boundary, where every tool result already passes for truncation.

Python
class ToolError(Exception):
    def __init__(self, what, retryable: bool, instead: str = ""):
        self.what, self.retryable, self.instead = what, retryable, instead

    def for_model(self) -> str:
        lines = [self.what]
        lines.append("Transient; retrying may succeed." if self.retryable
                     else "Not transient; the same call will fail identically.")
        if self.instead:
            lines.append(self.instead)
        return "\n".join(lines)

Making retryable a required argument is the whole point of the design. It forces whoever adds a tool to decide, at the time they have the knowledge, rather than leaving the model to infer it later without any.

Cap the retries in code anyway

Even with perfect error text, the retry limit belongs in your loop, not in the prompt. An instruction not to retry more than twice is a preference the model usually honours. A counter is a fact.

Python
if attempts[tool_name] >= 2:
    return ("This tool has now failed twice in this session. It is unavailable. "
            "Continue without it or tell the user what you could not do.")

Note what that message does: it closes the door and opens a different one. An error that only closes the door tends to produce a model that keeps knocking in slightly different ways (calling a neighbouring tool, reformatting the same argument) which burns as many turns as the retries did.

Do not paste the traceback

A Python traceback from a framework is commonly 400 to 800 tokens: a dozen stack frames through library internals, of which one line is informative. Pasting it costs real money on every failure and buries the useful line in the middle of the block, which is the worst position in the window.

Extract the exception type, the message, and the last frame that belongs to your own code. Four lines. Keep the full traceback in your logs, where a human will actually use it.

Do not leak internals either

Whatever you put in an error message enters the window, and whatever enters the window can be reproduced in the output. A connection string, an internal hostname, a file path revealing a customer’s name, a stack frame containing a token: all of these have shipped to end users this way, in systems whose authors would have been careful about it anywhere else.

Sanitise at the same boundary where you format. The rule is easy to state: an error string is user-visible until proven otherwise, because the model may well quote it.

The measurement

Log every tool error with its type, and log how many calls followed it before the agent either succeeded or stopped. That second number is the one to watch. A well-written error has a tail of one or two. A bad one has a tail of six, and the tail length tells you exactly which error messages to rewrite first, which is a far better use of an afternoon than rewriting the ones that merely look untidy.

Every lesson here is open. Your progress is recorded once you pass the Data, SQL and Getting to the Answer exam.

Check your understanding

Why does adding “retrying with the same arguments will fail” to an error message reduce cost more than shortening the message would?

Pick the one you would defend. Your answer stays private.

Next lesson · 9 minWhen the toolset outgrows the window

Stuck on something?Ask Sayali, your AI tutor

How to cite: Addaly (2026) ‘An error message is context too’, in Context Engineering. Edited by M. Mun. Addaly. Available at: https://addaly.com/learn/context-engineering/errors-that-teach (Accessed: 13 October 2026).