EQTY LabDocs

Errors

Every error code the proxy and the SDK return: when each happens, its HTTP status, its exception, whether clients retry it, and what to do.

View as Markdown

Every error the SDK raises is a ga.GuardianAgentError, with a code to branch on, a message for people, a hint saying what to do when there’s something to do, and the HTTP status the other end answered with, if it answered. No message repeats a value from a definition, prompt or file, which may be a credential: it says where the problem is and which rule it breaks.

Where errors reach you

  • Raised directly by the calls you make to the SDK: guardian.capabilities(), agent.register(), guardian.skill(), and the framework integrations when they make an agent.
  • Through your model client, as its own error type, such as openai.APIStatusError, for anything that goes wrong on a model request. That includes registering the agent on its first request, if you haven’t called agent.register() before. The client’s retries and error handling keep working, and ga.proxy_error(error) returns the SDK’s exception for it (When the proxy refuses a request). The client sees the status the proxy answered with, such as 413 for a definition that’s too large or 404 for an agent that isn’t enrolled. Only a failure with no error status of its own arrives as 502, and a request aimed elsewhere as 421. So branch on ga.proxy_error(), not on the status.

To have registration problems raised at start-up, where they’re easy to read, call agent.register(), or agent_of(agent).register() for an agent a framework integration made, before serving requests.

ga.proxy_error() returns None for errors that aren’t the proxy’s or the SDK’s: the provider’s, the Guardian Platform’s own refusals (below), and a connection error, which is how a proxy that can’t be reached looks through a client. The Errors API reference documents each class.

Catching them

The exceptions form three groups under ga.GuardianAgentError, so you can catch a group rather than each code:

  • ga.ProxyError: the proxy can’t be used: it’s down, incompatible, refusing callers from elsewhere, failed to reach the gateway or get an answer back, or answered in a way that can’t be used.
  • ga.AgentError: the proxy couldn’t match a request to an enrolled agent, its definition and its sub-agent.
  • ga.DefinitionError: the definition, or a skill’s or memory’s files, break a rule. Nothing was registered or stored.

ga.SigningFailed, ga.ModelNotRoutedThroughProxy and ga.UnrecognisedError sit directly under ga.GuardianAgentError.

Retries

OpenAI’s and Anthropic’s clients retry a failed request on their own. The SDK marks every refusal that retrying can’t fix, or could repeat, so the Python clients don’t retry it. They retry only these:

  • signing_failed, internal_error and upstream_unreachable, where nothing reached the model;
  • proxy_unreachable, which the client sees as a connection error.

Every other code this SDK knows is final. upstream_response_lost is final even though retrying might work, because the model may already have run. On a model request the proxy refuses, a code from a newer proxy (ga.UnrecognisedError) follows the proxy’s x-should-retry, or the client’s own rules on a proxy without it. When registering on a first request fails with such a code, the SDK answers the client itself, without the proxy’s mark, so the client’s own rules apply and may repeat the registration. Nothing reaches the model either way.

The SDK also handles one refusal itself: after definition_unknown, it registers the definition again and resends the request once.

Viper marks its own errors the same way, with an x-should-retry header, so clients in other languages follow these rules too (Retrying). The SDK still marks them itself, for proxies older than the header.

Connecting to the proxy

These come from ga.Guardian, the first time it asks the proxy what it supports, or from any later call to the proxy.

  • proxy_unreachable (ga.ProxyUnreachable, no status): nothing answered at VIPER_PROXY_URL, or not in time. Start viper proxy, and check the address it printed. Through a model client, it’s a connection error, which the client retries.
  • proxy_incompatible (ga.ProxyIncompatible, with the status the proxy answered, if any): the proxy doesn’t speak this SDK’s agent API, accept its definition schema, or offer a feature it relies on, so nothing was registered. The message names what’s missing. Through a model client, it arrives with the status the proxy answered: 404 or 405 from a proxy older than the agent API, or 502 from a proxy that answered but lacks what the SDK needs. OpenAI’s client raises openai.NotFoundError for the 404, and its base openai.APIStatusError for the 405. Check what your proxy offers with curl "$VIPER_PROXY_URL/__viper__/v1/capabilities", and upgrade Viper (Compatibility).
  • not_found (ga.NotFound, 404) and method_not_allowed (ga.MethodNotAllowed, 405): the proxy has no such endpoint, or not for that method. Viper and the SDK are from different releases, usually an older Viper: install releases that match (Compatibility).
  • not_local (ga.NotLocal, 403): the agent API refused a call that didn’t come from its own machine, addressed it as anything but localhost or a loopback IP, or came from a web page (with an Origin header). A model request naming an agent is refused the same way only for an Origin header. Call the proxy at 127.0.0.1 or localhost, from the same machine.
  • unexpected_response (ga.UnexpectedResponse, with the status, if any): the proxy answered with something the SDK can’t read, such as a response from another service at that address. Check VIPER_PROXY_URL names the proxy.
  • internal_error (ga.ProxyInternalError, 500): the proxy failed in a way the caller can’t fix, such as failing to store or read a definition or files. Its log says why. Through a model client it’s retried; raised directly, it isn’t.

Describing and registering an agent

The SDK checks a definition before sending it, so most of these are raised where the definition is made. The proxy checks it again when it’s registered, and has the last word on sizes (Limits).

  • definition_invalid (ga.DefinitionInvalid, 422): the definition breaks the schema or one of the proxy’s rules. Checked locally, problems lists each one, with the path to the value and the rule it breaks (When a definition breaks a rule). Refused by the proxy, problems is empty and the message names the first.
  • definition_contains_secret (ga.DefinitionContainsSecret, 422): the definition, or a skill’s or memory’s file, looks like it holds a credential, which is never recorded. Checked locally, problems names each place; the proxy’s message names the first. Remove it, or leave the field out with exclude (Credentials and exclude).
  • definition_too_large (ga.DefinitionTooLarge, 413): the definition, its prompts, or the registration request is over one of the proxy’s limits, which can’t be raised. The message names the part and its size, and the hint says what to do about that part (Limits):
    • The definition, or the request: “the limit can’t be raised: use fewer tools or sub-agents, or shorter descriptions, or split sub-agents into agents of their own”.
    • Sub-agents: “the limit can’t be raised: split sub-agents into agents of their own”.
    • An agent’s prompts, inline or stored: “shorten the prompts; long prompts are stored apart, but count toward this limit”.
    • An agent’s tools, skills, memory sources or MCP servers: “the limit can’t be raised: give the agent fewer tools, or split it into agents of their own”, naming the part over its limit.
    • A version, a framework’s name, an MCP server’s name, a memory backend or a model’s beta feature name: “shorten it”.
    • Fixed memory whose files, or their paths, are too many to sign together: “use fewer memory files, or shorter paths”.
    • An agent’s permissions, tool names and skills, too many or too long to sign together: “give the agent fewer or shorter permissions, tool names or skill names, or split it into agents of their own”.
    • A skill or memory stored with more files than an upload takes: “use a skill or memory with fewer files”.
    • A skill or memory stored with a file over the upload limit: “use a skill or memory with smaller files”.
  • schema_version_unsupported (ga.SchemaVersionUnsupported, 422): the proxy doesn’t accept this definition schema. Upgrade Viper.
  • agent_not_enrolled (ga.AgentNotEnrolled, 404): no agent of that name is enrolled on this proxy. The hint names the command, viper agent add <name>, then restart the proxy. Register the agent with viper agent register <name> too, if you haven’t.

Storing skill and memory files

guardian.skill(), guardian.instructions() and create_deep_agent’s skills and memory store their files on the proxy before the definition names them.

  • collection_invalid (ga.CollectionInvalid, 422): the files can’t be stored as given, such as a missing directory, a path outside it, a file more than 16 levels deep, or a file the proxy refuses. None were stored. The message names the file where it can.
  • collection_too_large (ga.CollectionTooLarge, 413): the files are over a limit on their number, a file’s size, their size together, or the size of their paths together (Limits).
  • definition_contains_secret (ga.DefinitionContainsSecret, 422): a file, or a file’s path, looks like it holds a credential.

Model requests

A model request names its agent, definition and sub-agent in headers the SDK adds (Proxy API). The proxy refuses one it can’t match, rather than sign it as someone else. These reach you through your model client.

  • agent_header_required (ga.AgentHeaderRequired, 403): the request names no agent, and the proxy runs with --require-agent-header. It’s also returned for a request that names a definition or sub-agent without an agent. Route the client through an agent with bind().
  • agent_header_invalid (ga.AgentHeaderInvalid, 400): an agent header was repeated, empty or not text. Don’t set Viper’s agent headers yourself: the SDK adds them.
  • agent_not_enrolled (ga.AgentNotEnrolled, 409 on a model request): the agent the request names has no key on this machine.
  • definition_unknown (ga.DefinitionUnknown, 409): the definition the request names isn’t registered for that agent, usually because the proxy restarted. The SDK registers it again and resends the request once, so you see this only if that fails. Likely causes: a proxy restarting over and over, several proxies behind one address, or something else setting X-Viper-Definition, such as a client’s default_headers.
  • subagent_unknown (ga.SubagentUnknown, 409): the definition has no sub-agent of that path. agent.subagent() checks the name before any request.
  • signing_failed (ga.SigningFailed, 502): the proxy couldn’t sign the request, so it didn’t send it. When the Guardian Platform refused the agent’s key while the proxy sent the request’s attestation, the hint says so: register the agent with viper agent register <name>, or check it hasn’t been revoked. Otherwise the proxy’s log says why. Retried, though retrying can’t help a refused key.
  • upstream_unreachable (ga.UpstreamUnreachable, 502): the proxy couldn’t connect to the Agent Gateway, so nothing was sent. Check the gateway is up, and that viper proxy --gateway-endpoint names it. Retried.
  • upstream_response_lost (ga.UpstreamResponseLost, 502, or 500): the request was sent, but no complete answer came back. The model may have run, so check before sending it again. Not retried.
  • request_unreadable (ga.RequestUnreadable, 400, or 413 in relay mode): the proxy couldn’t take in the request’s body. Send it again, or send less.
  • internal_error (ga.ProxyInternalError, 500): the proxy failed while handling the request. Retried.
  • model_not_routed_through_proxy (ga.ModelNotRoutedThroughProxy): raised by the SDK, never the proxy. A client or chat model points somewhere other than the proxy or its provider’s API, so its requests would go out unsigned. bind() raises it for a client or LangChain chat model aimed elsewhere, or a client with its own websocket_base_url, and the framework integrations raise it for a call that goes to a model the agent wasn’t bound to. A bound client’s request to anywhere but the proxy isn’t sent: the client gets a 421 refusal carrying this code. Give the client no base URL of its own, or the proxy’s.

The proxy marks every error it makes itself with an X-Viper-Error: 1 header, which it never passes on from the gateway or the provider. The refusals the SDK makes on its own, such as a failed registration on a first request or a misrouted request, carry the SDK’s own mark instead. ga.proxy_error() recognises both, so use it rather than checking headers.

An error for a request that names no agent, or one the proxy raises before it reads the agent headers (in relay mode, or while intercepting HTTPS), keeps the proxy’s older {"error": "<message>"} shape, and ga.proxy_error() returns None for it. Requests through bind() and httpx_client() always name an agent, and use neither mode.

Refusals the proxy passes on

Some refusals come from the Guardian Platform or the provider, and the proxy passes them on unchanged. ga.proxy_error() returns None for them, and your client’s own error says what happened:

  • The agent was revoked: 403, with code agent_revoked. An Organization Owner can restore it from its page in Guardian.
  • The Guardian Platform doesn’t know the key: 401, with code key_not_registered. Register the agent: viper agent register <name>.
  • The provider’s own errors, such as a bad provider key or a rate limit, come through as the provider sends them.

The Guardian Platform can also refuse the key earlier, while the proxy sends the request’s attestation. That arrives as signing_failed instead (above).

A newer proxy

ga.UnrecognisedError holds a code this SDK doesn’t know, from a proxy newer than the SDK. Its code, message and hint are the proxy’s. Upgrade the SDK to handle it by name.