# Exceptions

Most TnsAI exceptions extend `TnsAIException`, an unchecked exception that says whether a retry can help and how long to wait. An action call that fails throws `ActionExecutionException`, whose category names the cause.

## `TnsAIException`

| Method | Returns |
|---|---|
| `isRetryable()` | `true` for transient failures such as network errors, rate limits and server errors |
| `getErrorCode()` | `TNSAI-` plus the class name without `Exception`, upper-cased: `TNSAI-NETWORK`, `TNSAI-RATELIMIT` |
| `getSuggestedRetryDelayMs()` | `1000` when retryable and `0` otherwise, unless a subclass sets its own |
| `getMaxRetryAttempts()` | `3` when retryable and `0` otherwise, unless a subclass sets its own |

The error code is stable, so it works as a metric or log tag.

## `ActionExecutionException`

An action call that fails throws `ActionExecutionException`. It carries `getActionName()`, `getActionType()`, `getCategory()` and `getDetailedMessage()`, a one-line summary for logs.

| Category | Retryable | Cause |
|---|---|---|
| `NETWORK` | Yes | Connection timeout, DNS failure |
| `PARAMETER` | No | Missing parameter, wrong type |
| `CLIENT_ERROR` | No | HTTP 4xx response |
| `SERVER_ERROR` | Yes | HTTP 5xx response |
| `VALIDATION` | No | A failed contract, guardrail or validation check |
| `LLM` | Yes | Model error, quota exceeded |
| `MCP` | Yes | MCP tool error |
| `INVOCATION` | No | Reflection failure, method not found |
| `UNKNOWN` | No | Anything else |

Two cases land in `VALIDATION`:

- A failed `@Contract` clause. The cause is a `ContractViolationException` with `getContractKind()`, `getViolatedExpression()` and `getBindings()`. See [Contracts](contracts.md).
- An `LLM` action on an agent with no `LLMClient`. The call fails instead of returning `null`, so `@Fallback` can run. See [LLM actions](actions.md#llm-actions).

`ApprovalRequiredException` and security exceptions are not wrapped. They reach the caller as they are.

## `LLMException`

A failed model call throws `LLMException`. `getErrorType()` names the cause, and `getModel()` the model.

| `ErrorType` | Retryable | Cause |
|---|---|---|
| `MODEL_NOT_FOUND` | No | Model not found or unavailable |
| `AUTHENTICATION_FAILED` | No | Invalid API key |
| `CONTENT_FILTERED` | No | Content policy violation |
| `INVALID_REQUEST` | No | Invalid request |
| `MODEL_OVERLOADED` | Yes | Model overloaded; suggested delay 5000 ms |
| `CONTEXT_TOO_LONG` | No | Context length exceeded |
| `SERVER_ERROR` | Yes | Provider server error; suggested delay 2000 ms |
| `MALFORMED_TOOL_CALL` | No | The model sent bad JSON or missed a field in a tool call |
| `CAPABILITY_MISMATCH` | No | The model lacks a required capability |
| `UNKNOWN` | Yes | Anything else |

`LLMCapabilityException` extends `LLMException` for a feature the model or provider does not support, such as streaming, vision or structured output. `ToolCallNotSupportedException` extends it for tool calls.

## Other exceptions

| Exception | Retryable | Fields |
|---|---|---|
| `RateLimitException` | Yes, up to 5 times. The delay is the provider's `Retry-After`, or 60000 ms. | `getRetryAfterMs()`, `getService()`, `getRemainingQuota()` |
| `NetworkException` | Yes | host and port |
| `TimeoutException` | Yes | timeout and operation |
| `ValidationException` | No | |
| `KnowledgeNotReadyException` | No. It extends `ValidationException`. | The host class, the source and the `Problem`: `MISSING`, `EMPTY` or `UNREADABLE` |
| `ApprovalRequiredException` | No | action name and reason |
| `TaskCompleteException` | No | Not implemented: no framework loop catches it, and `@CompletionSignal` is not read. |

## Handle a failure

Choose a recovery from the category of an action failure. For a chat turn, catch the most specific type first.

<!-- java-contract: src/main/java/com/example/tnsai/docs/ExceptionsExample.java -->
```java
package com.example.tnsai.docs;

import com.tnsai.agents.Agent;
import com.tnsai.exceptions.ActionExecutionException;
import com.tnsai.exceptions.LLMException;
import com.tnsai.exceptions.RateLimitException;
import com.tnsai.exceptions.TnsAIException;

import java.util.Map;

public final class ExceptionsExample {
    private ExceptionsExample() {}

    // An action call: choose a recovery from the category.
    static Object forecast(Agent agent) {
        try {
            return agent.executeAction("forecast", Map.of("city", "Paris"));
        } catch (ActionExecutionException e) {
            System.err.println(e.getDetailedMessage());
            return switch (e.getCategory()) {
                case NETWORK, SERVER_ERROR -> "retry in " + e.getSuggestedRetryDelayMs() + " ms";
                case PARAMETER, VALIDATION -> "fix the arguments for " + e.getActionName();
                default -> throw e;
            };
        }
    }

    // A chat turn: catch the most specific type first.
    static String ask(Agent agent, String question) {
        try {
            return agent.chat(question);
        } catch (RateLimitException e) {
            return "rate limited, retry in " + e.getSuggestedRetryDelayMs() + " ms";
        } catch (LLMException e) {
            if (e.getErrorType() == LLMException.ErrorType.CONTEXT_TOO_LONG) {
                return "shorten the question";
            }
            throw e;
        } catch (TnsAIException e) {
            System.err.println(e.getErrorCode() + " retryable=" + e.isRetryable());
            throw e;
        }
    }
}
```

To retry inside the framework instead, see [Resilience](resilience.md).

## Related

- [Actions](actions.md): the steps where an action call can fail.
- [Contracts](contracts.md): the clauses behind `VALIDATION`.
- [Resilience](resilience.md): retries, timeouts and fallbacks.
- [Knowledge](../rag/index.md): sources that raise `KnowledgeNotReadyException`.
