Open source
An LLM agent is good at deciding what to do and bad at handling what happens when the call fails. Most agents react to every failure the same way — re-prompt the model, or blindly retry. Both are wrong most of the time. Here's the smaller idea that fixes it, and the tiny open-source library that implements it.
klanex engineering · August 2026 · ~6 min read
When a tool call fails, the model gets back a raw 403, a swallowed
exception, or a two-page HTML error, and reacts on instinct. The instinct is almost
always one of two moves — re-prompt itself, or retry — and both are usually the
wrong one:
503. Burns tokens, and worse, invites
the model to "fix" a payload that was already correct. The API was down; the
arguments were fine.
400 forever. The request is malformed. The
tenth identical attempt fails exactly like the first.
401. No edit to the body
will ever satisfy an expired token.
The mistake underneath all three is the same: reacting to the HTTP status instead of to what it means for the agent.
A 429 and a 400 are both "the call failed," but they demand
opposite responses: wait and try again unchanged, versus stop and fix the request.
The useful axis was never the number — it's the reaction the number should trigger.
There turn out to be exactly three, plus an honest fourth for "I don't know":
5xx). Retry with backoff; the model changes nothing.
Map every failure to one of those four classes and "what should the agent do" stops being a per-tool judgment call. Eight codes cover the HTTP reality:
# failure class → what the agent should do
transient → retry with backoff, unchanged
RATE_LIMITED · TIMEOUT · UNAVAILABLE
self_correctable → re-prompt with the hint, resend
SCHEMA_INVALID · INVALID_REQUEST · NOT_FOUND
auth → stop; fix credentials, don't touch args
UNAUTHORIZED
indeterminate → escalate to a human
UNKNOWN
"Retryable" is just shorthand for the transient class — the only one you should ever retry unchanged. Everything else is a bug in the request, a problem with your credentials, or something a human needs to look at.
Classifying the failure is half of it. The other half is telling the model what to do in words you can paste straight into its context — and the most valuable hint is often the one that tells it to do nothing:
# the model's instinct: "let me fix my arguments and try again"
"The API rejected this call as unauthorized (HTTP 401). This is a
credentials or permissions problem, NOT a problem with your
arguments — do not modify them. A human or the orchestrator must
fix authentication before retrying."
A transient or auth hint that fails to say "don't change the request" is exactly how a good payload gets mangled on the retry. Telling the model what not to touch is as important as telling it what to fix.
This is small enough that you could write it yourself — and everyone does, slightly differently, in every tool. So we pulled it out into agenterr: a dependency-free library that classifies a tool-call result and hands back the code, the class, whether to retry, and the hint.
out := agenterr.Classify(resp.StatusCode, body, err)
switch {
case out.OK: // 2xx — nothing to do
case out.Retryable: // transient — retry, don't touch args
case out.Class == agenterr.ClassSelfCorrectable: // re-prompt with out.LLMHint
default: // auth / unknown — escalate
}
It's Apache-2.0, has no dependencies, and ships with a language-agnostic spec so ports
(Python and TypeScript next) classify identically. It also handles the parts people
skip: transport-error timeouts, Retry-After parsing, bounded body
snippets in the hint, and a pre-flight SchemaInvalid path so you can catch
a bad payload before you even make the call.
agenterr classifies and advises. It never makes the call, retries, backs off, breaks circuits, or persists anything — and it never will. It's a decision function, not an execution engine.
That line matters, because the moment you act on Retryable: true
in a way that has to survive a process restart, not block your agent for ten minutes,
and leave an audit trail, you've stopped classifying and started building durable
execution. That's a different — and much larger — problem. (We wrote up why agents
fail at exactly that,
five ways over.)
You can build that engine yourself on top of agenterr. Or you can hand the whole thing to klanex, which runs this taxonomy as a service: submit the tool call as an intent and it does the durable retries, per-host circuit breaking, credential sealing, and signed webhooks — returning these exact codes and hints. agenterr is the open taxonomy; klanex is the managed engine that acts on it.
agenterr is open source and free — drop it into your agent loop, or read the spec. When you need the durable execution behind it, klanex is the managed version.