Agent errors
Status: draft 0.1. Covers friction #26 (errors come back as red text on a page). Reference implementation: agent_index/errors.py, used by the site middleware, the agent client and the fetcher.
Every failure an agent can hit comes back as JSON with a stable code, the field at fault when there is one, and what to do next:
{"error": {"code": "out_of_stock", "message": "Sold out", "field": "product_id", "retry_after": null}}
| Code | Agent should |
|---|---|
invalid_input |
Fix the named field and retry. |
missing_field |
Supply the named field and retry. |
not_found |
Stop; the thing doesn't exist. |
not_allowed |
Stop; the site's agent policy doesn't allow this use here. |
conflict |
Re-read the current state, then retry. |
out_of_stock |
Stop or choose another item. |
auth_required |
Get a grant for this site. |
scope_missing |
Ask the person for a grant with the named scope. |
payment_required |
Pay the advertised price (see payments). |
payment_declined |
Ask the person. |
rate_limited |
Wait retry_after seconds. |
temporarily_unavailable |
Wait and retry. |
needs_human |
Hand the step to the person. Used for anything only a human can do, so agents stop retrying it. |
internal |
Retry once. |
Sites may add their own codes; agents treat unknown codes as internal. Identity and payment checks add their own codes (see those specs), with the same shape.
Use the HTTP status that matches (400, 401, 402, 403, 404, 409, 429, 503) so tools that only read the status still behave sensibly.
The retry field
Every error carries retry, one word an agent can switch on: fix_input, no, refetch, get_grant, pay, ask_human, after_delay or once. Codes from the identity, payment and negotiation specs have the same shape and their own retry values (errors.CODES lists them all). A site's own codes are passed through unchanged and advised as once.
rate_limited and temporarily_unavailable responses also send a Retry-After header. The agent client waits it out once when it is 30 seconds or less, and otherwise hands the error back with retry_after so the agent can do something else meanwhile.
Sites that don't speak this yet
errors.from_response(status, headers, body) turns any response into this shape. Structured JSON is read as is. Otherwise the code comes from the status (404 not_found, 429 rate_limited, 503 temporarily_unavailable...), refined by the page text where that is safe: a CAPTCHA page becomes needs_human, "sold out" becomes out_of_stock. A server failure stays internal whatever its page says. The message is the page title, never a stack trace.
AgentFriendly applies the same conversion to the wrapped app's own error pages whenever the caller is an agent (signed, or asking for JSON or Markdown), so a site gets structured errors without touching its error handling. fetch returns code, retry and advice with every failure, and when it serves an older copy because the site failed, stale_because says why.