Documentation
¶
Overview ¶
Package agenterr classifies the failures of an AI agent's tool calls into a small, stable taxonomy — so an agent (or the orchestration around it) can tell at a glance whether to retry, re-prompt the model to fix its arguments, or stop and escalate. It has one job and no dependencies.
The problem ¶
When an agent's tool call fails, the model gets a stack trace, a raw 4xx, or a swallowed exception — and has no principled way to decide what to do next. Re-prompting on a 503 wastes tokens and corrupts good arguments; blindly retrying a 400 loops forever; retrying a 401 will never succeed no matter how the payload is rewritten. The useful question is not "what is the HTTP status" but "how should the agent respond".
The taxonomy ¶
Every failure maps to one Code, and every Code to one Class — three mutually exclusive reactions plus an explicit unknown:
- ClassTransient (RATE_LIMITED, TIMEOUT, UNAVAILABLE): not the request's fault. Retry with backoff; the model changes nothing.
- ClassSelfCorrectable (SCHEMA_INVALID, INVALID_REQUEST, NOT_FOUND): the arguments are fixable. Feed Outcome.LLMHint back to the model and resend.
- ClassAuth (UNAUTHORIZED): credentials or scope, not the payload. Do not let the model rewrite the arguments; fix auth out of band.
- ClassIndeterminate (UNKNOWN): could not be classified. Escalate.
Usage ¶
Classify a tool call's result and branch on the response class:
out := agenterr.Classify(resp.StatusCode, body, err)
switch {
case out.OK:
// success — nothing to do
case out.Retryable:
// transient — retry with backoff; leave the request untouched
case out.Class == agenterr.ClassSelfCorrectable:
// feed out.LLMHint back into the model's context, then resend
default:
// auth or unknown — escalate; do not let the model rewrite the payload
}
The library only decides and advises. It never performs the call, retries, or persists anything: that durable-execution machinery is deliberately out of scope (see the README). SchemaInvalid lets you emit the same taxonomy for a pre-flight validation failure without making a call at all.
See SPEC.md for the language-agnostic contract that ports must satisfy.
Example ¶
The canonical agent loop: classify a tool call's result, then react by class — retry, re-prompt, or escalate — without re-inventing that logic per tool.
package main
import (
"fmt"
"github.com/chrassy/agenterr"
)
func main() {
// Pretend a tool call to some API came back 422 with a validation message.
status, body, err := 422, `{"error":"amount must be a positive integer"}`, error(nil)
out := agenterr.Classify(status, body, err)
switch {
case out.OK:
fmt.Println("done")
case out.Retryable:
fmt.Println("retry with backoff, don't touch the arguments")
case out.Class == agenterr.ClassSelfCorrectable:
fmt.Println("re-prompt the model with this hint, then resend:")
fmt.Println(out.LLMHint)
default:
fmt.Println("escalate:", out.Code)
}
}
Output: re-prompt the model with this hint, then resend: The API rejected this call as invalid (HTTP 422). Your arguments are wrong in a way you can fix: correct them to satisfy the API's requirements and resend. The API said: {"error":"amount must be a positive integer"}
Index ¶
Examples ¶
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
Types ¶
type Class ¶
type Class string
Class groups codes by how an agent should respond to them. It is the heart of the taxonomy: the four classes are three mutually exclusive reactions (retry, re-prompt, escalate) plus an explicit "don't know".
const ( // ClassTransient: the failure is temporary and not caused by the request. // The caller should retry with backoff; the model must not change anything. ClassTransient Class = "transient" // ClassSelfCorrectable: the request was wrong in a way the model can fix. // Feed Outcome.LLMHint back into the model's context and resend. ClassSelfCorrectable Class = "self_correctable" // ClassAuth: an authentication or permission problem. The payload is not at // fault; a human or the orchestrator must fix credentials or scope. Do not // let the model rewrite the arguments. ClassAuth Class = "auth" // ClassIndeterminate: the failure could not be classified. Surface it to a // human rather than blindly retrying. ClassIndeterminate Class = "indeterminate" )
type Code ¶
type Code string
Code is a stable, machine-readable classification of a tool-call failure. Every Code belongs to exactly one Class, which is what tells an agent how to respond. Codes and their metadata are the public contract of this package; see SPEC.md for the language-agnostic definition.
const ( // CodeSchemaInvalid: the arguments the model produced failed a pre-flight // schema or shape check, before any call was made. Self-correctable. CodeSchemaInvalid Code = "SCHEMA_INVALID" // CodeInvalidRequest: the API rejected the request as malformed or // semantically invalid (typically 400 or 422). Self-correctable. CodeInvalidRequest Code = "INVALID_REQUEST" // CodeNotFound: the API returned 404 — a referenced resource does not // exist (often a hallucinated identifier). Self-correctable. CodeNotFound Code = "NOT_FOUND" // permission reasons (401 or 403). Not a payload problem. CodeUnauthorized Code = "UNAUTHORIZED" // CodeRateLimited: the API is rate-limiting the caller (429). Transient. CodeRateLimited Code = "RATE_LIMITED" // CodeTimeout: the API did not respond in time — a 408 or a client-side // timeout with no response. Transient. CodeTimeout Code = "TIMEOUT" // a transport/network failure. Transient. CodeUnavailable Code = "UNAVAILABLE" // CodeUnknown: the result could not be classified. Treat conservatively. CodeUnknown Code = "UNKNOWN" )
func Codes ¶
func Codes() []Code
Codes returns every defined code in a stable order (severity-agnostic; grouped self-correctable, auth, transient, indeterminate). Useful for docs, tests, and building UIs.
func (Code) Meta ¶
Meta returns the fixed metadata for c. An unrecognized code returns the metadata for CodeUnknown, so callers never get a zero Meta.
type Meta ¶
type Meta struct {
Code Code
Class Class
Retryable bool
Summary string // one-line description of the failure
AgentAction string // what the agent or orchestrator should do about it
}
Meta is the fixed, human-readable metadata for a Code.
type Outcome ¶
type Outcome struct {
// OK is true when there was nothing to classify: a 2xx result, or a nil
// error passed to FromError/Classify. The other fields are then zero.
OK bool `json:"ok"`
// Code is the classification. Empty only when OK is true.
Code Code `json:"code,omitempty"`
// Class mirrors Code.Class(); included so logs and cross-language consumers
// don't need the registry to interpret the outcome.
Class Class `json:"class,omitempty"`
// Retryable mirrors Code.Retryable(): true for the transient class only.
Retryable bool `json:"retryable"`
// LLMHint is phrased to be injected verbatim into the model's context. It
// is present for every failure — for transient and auth codes it tells the
// model NOT to change the request, which is just as important as telling it
// how to fix one.
LLMHint string `json:"llm_hint,omitempty"`
// RetryAfter is the server-advised minimum delay before retrying, parsed
// from a Retry-After header by FromResponse (0 when absent or not parsed).
RetryAfter time.Duration `json:"-"`
// Status is the HTTP status that produced this outcome, or 0 for a
// transport-level failure or a pre-flight (SchemaInvalid) outcome.
Status int `json:"status,omitempty"`
}
Outcome is the classification of a single tool-call attempt. It is the value you branch on: OK short-circuits success, Retryable covers the transient class, and Class distinguishes self-correctable from auth/unknown failures.
func Classify ¶
Classify is the combined entry point. A non-nil err is treated as a transport-level failure (no response) and takes precedence over status/body; otherwise the call is classified by status and body.
func FromError ¶
FromError classifies a transport-level failure that produced no HTTP response — a client timeout, connection refused, DNS failure, etc. A context deadline or any net.Error reporting Timeout() maps to TIMEOUT; everything else maps to UNAVAILABLE. A nil error returns Outcome{OK: true}.
Example ¶
A transport failure with no HTTP response classifies from the error alone.
package main
import (
"errors"
"fmt"
"github.com/chrassy/agenterr"
)
func main() {
out := agenterr.FromError(errors.New("dial tcp: connection refused"))
fmt.Println(out.Code, "status:", out.Status)
}
Output: UNAVAILABLE status: 0
func FromResponse ¶
FromResponse classifies an *http.Response. Beyond FromStatus it reads the Retry-After header (seconds or HTTP-date) into Outcome.RetryAfter and quotes a bounded snippet of the body into the hint. It reads up to a small cap from resp.Body and does NOT close it — the caller still owns the body.
func FromStatus ¶
FromStatus classifies a tool call by its HTTP status code. body (which may be empty) enriches the hint for a self-correctable failure; it is trimmed, single-lined, and length-capped before being quoted. A 2xx status returns Outcome{OK: true}.
Example (Auth) ¶
An auth failure is NOT self-correctable: rewriting the payload can't fix it.
package main
import (
"fmt"
"github.com/chrassy/agenterr"
)
func main() {
out := agenterr.FromStatus(403, "")
fmt.Println(out.Code, out.Class, "retryable:", out.Retryable)
}
Output: UNAUTHORIZED auth retryable: false
Example (Transient) ¶
A transient failure: the hint tells the model to leave the request alone.
package main
import (
"fmt"
"github.com/chrassy/agenterr"
)
func main() {
out := agenterr.FromStatus(503, "upstream connect error")
fmt.Println(out.Code, "retryable:", out.Retryable)
}
Output: UNAVAILABLE retryable: true
func SchemaInvalid ¶
SchemaInvalid builds a SCHEMA_INVALID outcome from a list of human-readable validation problems and formats them into an LLMHint the model can act on. Use it when arguments fail your own pre-flight schema check, before any call is made — catching the mistake here saves a network round trip. The library performs no JSON Schema validation itself; bring your own validator and pass its messages.
Example ¶
Catch a bad payload before spending a network round trip on it.
package main
import (
"fmt"
"github.com/chrassy/agenterr"
)
func main() {
out := agenterr.SchemaInvalid(`at "/": missing properties: 'charge_id'`)
fmt.Println(out.Code)
fmt.Println(out.LLMHint)
}
Output: SCHEMA_INVALID The arguments you generated do not match the required schema. Fix the following and resend: - at "/": missing properties: 'charge_id'
func (Outcome) Failed ¶
Failed reports whether the outcome represents a failure (the inverse of OK).
func (Outcome) MarshalJSON ¶
MarshalJSON renders RetryAfter as whole seconds under "retry_after_seconds", so the serialized shape matches SPEC.md across language ports.