Skip to content
Typed exception hierarchy
Public Preview

SDK exceptions, error codes, and task outcomes

Reference for Duale AI SDK exceptions, HTTP rejection codes, task outcome error codes, and problem details used to classify and retry failures.

This page lists SDK exception types, problem details fields, HTTP rejection codes, task outcomes, document ingestion failures, and Library and Tool Host error codes.

  • Typed exception hierarchy and exceptions outside it.
  • Problem details fields and HTTP rejection codes.
  • Task outcome error codes and stream error context.
  • Document ingestion failure codes.
  • Library request codes and Tool Host publication errors.

Summaries were generated by AI.

SDK operations raise both SDK exceptions and exceptions from Python, Pydantic, or a custom transport. This page lists each exception, the problem details Duale AI returns, every error code, and the automatic retries. To go from a symptom to a fix, use Troubleshoot the SDK. Task attachments use the Library errors.

Typed exception hierarchy

DualeAIError is the base class for the typed exceptions below. Import them from dualeai.

ExceptionRaised when
DualeAIErrorBase class. A failed Task is raised as DualeAIError with problem details attached. A stopped Task raises TaskStoppedError instead.
ValidationErrorA result does not match the response model passed through res=.
DualeAIAuthErrorThe server rejected the token or the caller lacks the required permission, including a refused continuation or Stop. A refused continuation is observed through its response; Stop raises at the call.
BusinessErrorA valid HTTP request failed a business rule, including Library 4xx errors other than 401 and 403.
LibraryUploadErrorA local file read or an upload part failed.
ConfigurationErrorA Library operation needs configuration, such as DUALEAI_TENANT_ID, that is missing.
MessagingErrorBase class for SDK transport failures.
DualeAIConnectionErrorA Task stream or network request failed, a Library call returned 5xx, or platform-token issuance failed outside 401/403 (subclass of MessagingError).
TaskStoppedErrorAn accepted Stop took effect before the Task produced a result. Read exc.reason for the caller-supplied reason.

The default HTTP transport reports Task-stream network failures as DualeAIConnectionError after any applicable retries. This reports a failure to observe the outcome; it does not prove that the Task failed. Preserve the Task ID and read the stream error context.

LibraryUploadError carries the failed input’s attachment_key and optional part_number. completed_receipts contains receipts that finished earlier in the same batch.

TaskStoppedError reports only reason and task_id. Stop is a separate final outcome with no problem details.

Exceptions outside the hierarchy

  • A submission rejected by the open circuit breaker raises RuntimeError.
  • Invalid client configuration raises pydantic.ValidationError when you construct DualeAIConfig directly. When DualeAISDK() creates an invalid default config from the environment, it wraps that failure in RuntimeError.
  • Missing attachment paths raise FileNotFoundError. A directory or empty file raises ValueError during preparation. Attachment metadata that violates its model constraints raises pydantic.ValidationError.
  • Invalid Library update or polling arguments, or a non-empty task attachment upload without an Agent Identity identifier, raise ValueError.
  • sdk.libraries.wait_for_document() raises the built-in TimeoutError when the whole polling operation exceeds its client-side timeout.
  • A cached @activity whose wait for scheduled work exceeds job_timeout raises the built-in TimeoutError. That timer excludes cache reads, cache writes, and any wait to enter a full queue; see Activity caching.
  • Custom transports can raise exceptions outside this hierarchy.

Problem details

Read exc.problem_details to classify a failure, decide whether to retry, or report it. This RFC 9457 object is attached to DualeAIError for a failed Task, and to DualeAIAuthError, BusinessError, or DualeAIConnectionError for an HTTP rejection of a Task stream or Library call.

Platform-token failures preserve valid problem details from the server; missing or malformed details leave problem_details=None. Local validation, configuration, and file errors have no server details.

FieldPresent onMeaning
error_code, titleEvery problemStable error identifier, for example TASK_DEADLINE_EXCEEDED, AUTHORIZATION_FAILED, BILLING_LIMIT_EXCEEDED, or CONTENT_FILTERED, and its short summary.
detailEvery problemHuman-readable description. For a failed Task, it is the fixed public text of its error_code.
retryableEvery problemWhether a repeat can succeed.
type, statusFailed Tasks and most HTTP rejectionsThe RFC 9457 problem type and status.
error_categoryWhen Duale AI classifies the causeOne of transient, upstream, integrity, or config.
owner_action_requiredConfiguration failuresThe failure needs an account or configuration change, not a retry.
trace_id, span_idWhen availableCorrelation identifiers to send to support.
request_id, instanceMost HTTP rejections; never a failed TaskThe rejected request’s identifiers.
errors[]Validation and some other HTTP rejections; never a failed TaskPer-error details. Each item requires message and details; field and ai_hints are optional. ai_hints belongs to each item.
retry_after_secondsSome HTTP rejections; never a failed TaskSuggested wait before a retry.

Branch on error_code, not on the detail text. Before your application repeats failed work, require retryable=true. For an HTTP rejection, also honor retry_after_seconds when present. A failed Task never carries retry_after_seconds, so apply your own backoff. Automatic retries describes what the SDK already repeats for you.

HTTP rejection codes

An HTTP rejection arrives through DualeAIAuthError, BusinessError, or DualeAIConnectionError, according to the exception hierarchy. For a Task submission, await the returned response to observe it. An HTTP status alone does not promise a retryable value. Read the received field before deciding to retry.

CodeStatusRetryableMeaningNext action
AUTHENTICATION_FAILED401Read response fieldThe credential was rejected.Check or replace the credential.
AUTHORIZATION_FAILED403Read response fieldThe caller lacks permission.Check Task actions or Library actions.
NOT_FOUND404Read response fieldThe requested resource was not found.Check its identifier and scope.
CONFLICT409Read response fieldThe request conflicts with the resource’s current state.Read detail and reconcile that state.
BAD_REQUEST400Read response fieldThe request is invalid.Correct the request.
VALIDATION_ERROR422Read response fieldThe request fails validation.Read errors[] and correct the named fields.
RATE_LIMIT_EXCEEDED429Read response fieldThe operation exceeded its rate limit, including Tool Host lifecycle calls.Retry later when permitted by the response.
SERVICE_UNAVAILABLE503Read response fieldA required service is unavailable.Retry when retryable=true.
INTERNAL_ERROR500Read response fieldDuale AI failed to complete the request.Retry only when retryable=true; preserve correlation identifiers.

METHOD_NOT_ALLOWED (405), NOT_ACCEPTABLE (406), GONE (410), REQUEST_BODY_TOO_LARGE (413), and UNSUPPORTED_MEDIA_TYPE (415) name a request Duale AI cannot accept as sent; correct the request.

Task outcomes

A failed Task is raised by model() as DualeAIError. Its status describes the failed Task; it is not an HTTP rejection of the stream. A stopped Task raises TaskStoppedError instead.

CodeStatusRetryableMeaningNext action
TASK_DEADLINE_EXCEEDED504falseThe Task did not finish before its deadline.Continue it, or submit it again, with a later deadline; see deadlines.
TASK_EXPIRED410falseThe Task is past its 30-day lifetime.Start a new Task.
TASK_STOPPED409falseThe Task was stopped.Submit or continue a Task if more work is needed.
BILLING_LIMIT_EXCEEDED429falseThe Tenant’s billing limit prevented the work.Resolve the billing limit before submitting again.
CONTENT_FILTERED400falseNo selected model produced an acceptable response.Change the request.
LLM_CONFIG_UNAVAILABLE503falseNo configured model can process this request.Ask the configuration owner to choose suitable models.
LLM_CONFIG_CONTEXT_TOO_SMALL503falseThe request fits no configured model’s context window.Configure a larger context window or send a smaller request; see model selection.
LLM_PROVIDERS_UNAVAILABLE503trueProviders stayed unavailable through the automatic retries.Submit again later; see provider outages.
LLM_NO_PROGRESS422falseThe model did not reach an answer: it reached its limit on model turns, repeated the same work, or stopped acting.Narrow the instruction or use a more capable model.
CONTINUATION_UNAVAILABLE410falseThe Task can no longer be continued: its chain no longer holds the history it needs, for example because the first Task of the chain is older than 90 days.Start a new Task; see how long history stays usable.
RESULT_UNAVAILABLE500falseThe Task finished its work but did not produce its result.Continue the Task within 30 days of its submission to get the result.
PARENT_TASK_NOT_FOUND404falseThe Task to continue is older than 30 days and can no longer be continued.Start a new Task; see how long history stays usable.
PARENT_TASK_UNAVAILABLE409trueThe Task to continue has not produced usable work yet, or no Task with this identifier exists for this Agent Identity. The continuation waited until it was too late to start.Check the parent Task named in the request, then submit the continuation later with a new identifier; see when a continuation waits.
INTERNAL_ERROR500Read response fieldDuale AI failed to complete the Task.Retry only when retryable=true; preserve correlation identifiers.

Codes about the models you configure start with LLM_.

Task-stream error context

When the default HTTP transport cannot recover a Task stream, read exc.context as well as exc.problem_details. The context describes the last observation failure. Its error_code can name a local or end-to-end encryption error that has no server problem details.

Context fieldMeaning
operationtask_run, task_continue, or tool_results.
task_idThe Task whose stream the SDK was consuming.
retry_attemptZero-based attempt index: 0 is the initial attempt, and 4 is the last possible attempt. A non-retryable error can stop sooner.
metadata["http_status"]The received HTTP status, when known. It is independent of a status declared in problem_details.status.
metadata["logical_method"]The method of the request the attempt sent. Credential renewal can fail before that request is sent.
metadata["last_event_id"]The last event the SDK received, when available.
  • Context field
    operation
    Meaning
    task_run, task_continue, or tool_results.
  • Context field
    task_id
    Meaning
    The Task whose stream the SDK was consuming.
  • Context field
    retry_attempt
    Meaning
    Zero-based attempt index: 0 is the initial attempt, and 4 is the last possible attempt. A non-retryable error can stop sooner.
  • Context field
    metadata["http_status"]
    Meaning
    The received HTTP status, when known. It is independent of a status declared in problem_details.status.
  • Context field
    metadata["logical_method"]
    Meaning
    The method of the request the attempt sent. Credential renewal can fail before that request is sent.
  • Context field
    metadata["last_event_id"]
    Meaning
    The last event the SDK received, when available.

These diagnostics do not establish a remote final outcome. Initial connection failures, other operations, and custom transports can provide less context. Include the available operation, Task ID, attempt index, and HTTP status when reporting a persistent stream failure.

Document ingestion failures

wait_for_document() returns a failed document instead of raising when processing fails. Its document.failure can be None, so a failure code is not always available. A failed document will not become ready on its own.

When details are present, they use RFC 9457 with retryable=false; optional fields can be absent or null. Treat missing details or an unlisted error_code as an unknown cause. Upload again only if a new upload can change the cause below.

error_codeWhat happenedYour next action
FORMAT_UNSUPPORTEDDuale AI does not support this file type.Convert the file to a supported type, then upload the result.
CORRUPT_DOCUMENTThe file is damaged and cannot be parsed.Re-export the file from its source application.
ENCRYPTED_DOCUMENTThe file is an encrypted legacy .doc or .ppt, or an encrypted .epub.Remove the password, then upload the file again. A protected .docx, .xlsx, or .pptx reaches ready instead; see Libraries and documents.
DOCUMENT_TOO_LARGEThe file met the 500 MiB upload limit but exceeded a scanning or extraction limit.Upload a smaller or simpler source. Splitting helps only when file size was the cause.
MALWARE_DETECTEDThe upload scan matched a malware signature.Do not upload it again. Duale AI keeps the scanned file with the failed document. Investigate the file at its source.
HASH_MISMATCH, SIZE_MISMATCHThe stored bytes did not match what the upload declared.Upload the file again from an unchanged local copy.
EXPIRED_UPLOADThe upload session expired before the file finished uploading.Start a new upload and finish it within the session.
LIBRARY_DELETEDThe Library was deleted while the document was still being processed.Upload into a Library that is not deleted.
LIBRARY_ACCESS_REVOKEDThe uploading Agent Identity lost library:write on the Library’s path while the document was being processed.Restore that Agent Identity’s library:write on the Library’s current path, then upload again.
EXTRACTION_FAILEDThe file produced neither searchable text nor a searchable picture, or extraction failed for a cause detail names.Read detail: it separates an empty document from a parse failure. Check that the file opens and contains readable text or a picture Duale AI can decode. The same file rarely gives a different result.
PROCESSING_TIMEOUTExtraction ran past its deadline.Upload a smaller or simpler source.
DOCUMENT_PROCESSING_FAILEDDuale AI failed to process the document.Upload the same file again. Report it with the request_id if it repeats.
DOCUMENT_UPLOAD_CANCELLEDThe first upload was cancelled before Duale AI verified its source.Upload the file again.

A failed document still occupies its Library until you delete it.

Library request codes

A Library call can also fail before any document is processed. These codes arrive in problem_details.error_code.

CodeStatusMeaningNext action
ACR_STEPUP_REQUIRED401The operation needs a higher authentication level, such as deleting a whole Library.Use the Dashboard; an API token cannot reach that level.
DOCUMENT_UPLOAD_TOO_LARGE413A document exceeds the 500 MiB upload limit.Reduce or split the document before preparing and uploading it again.
DOCUMENT_TAGS_TOO_LARGE413Document tags exceed 4 KiB of serialized JSON.Shorten the tags; see Library limits.
TAGS_TOO_LARGE422Library tags exceed 4 KiB of serialized JSON.Shorten the tags; see Library limits.
UPLOAD_SESSION_EXPIRED410The upload session expired before completion.Start a new upload.
DOCUMENT_PURGED410The document can no longer be restored.Upload the file again.
CONFLICT409With detail Library changed; retry against the current path: the Library changed while the call ran, and the operation did not run.Read the Library again, because its current path decides access, then repeat the call against that path if you are still permitted to.

Status

ACR_STEPUP_REQUIREDDOCUMENT_UPLOAD_TOO_LARGEDOCUMENT_TAGS_TOO_LARGETAGS_TOO_LARGEUPLOAD_SESSION_EXPIREDDOCUMENT_PURGEDCONFLICT
401413413422410410409

Meaning

ACR_STEPUP_REQUIREDDOCUMENT_UPLOAD_TOO_LARGEDOCUMENT_TAGS_TOO_LARGETAGS_TOO_LARGEUPLOAD_SESSION_EXPIREDDOCUMENT_PURGEDCONFLICT
The operation needs a higher authentication level, such as deleting a whole Library.A document exceeds the 500 MiB upload limit.Document tags exceed 4 KiB of serialized JSON.Library tags exceed 4 KiB of serialized JSON.The upload session expired before completion.The document can no longer be restored.With detail Library changed; retry against the current path: the Library changed while the call ran, and the operation did not run.

Next action

ACR_STEPUP_REQUIREDDOCUMENT_UPLOAD_TOO_LARGEDOCUMENT_TAGS_TOO_LARGETAGS_TOO_LARGEUPLOAD_SESSION_EXPIREDDOCUMENT_PURGEDCONFLICT
Use the Dashboard; an API token cannot reach that level.Reduce or split the document before preparing and uploading it again.Shorten the tags; see Library limits.Shorten the tags; see Library limits.Start a new upload.Upload the file again.Read the Library again, because its current path decides access, then repeat the call against that path if you are still permitted to.

Tool Host errors

sdk.serve() publishes the whole manifest. The four publication rejections below publish nothing and raise BusinessError without retrying. Read problem_details.error_code; Tool limits lists the limits.

CodeStatusMeaningNext action
TOO_MANY_TOOLS422The manifest declares more Tools than one Tool Host can publish.Serve fewer Tools from this process, or split them across Tool Hosts under separate Agent Identities.
REQUEST_BODY_TOO_LARGE413The registration request is larger than 4 MiB. The same code rejects an oversized Tool Result.Shorten schemas and descriptions, or split the Tools across Tool Hosts under separate Agent Identities.
TOOL_SCHEMA_TOO_LARGE422One @tool input schema is larger than the per-Tool ceiling.Narrow that Tool’s parameter model and shorten its field descriptions.
RESERVED_TOOL_NAME422A @tool function uses a reserved name: exec_agent, finish, list_agents, search_available_tools, context_ls, context_search, context_grep, context_cat, conversation_ls, conversation_search, conversation_grep, or conversation_cat. The Python SDK refuses such a name locally: @tool raises ValueError when it registers the function, before sdk.serve() sends any manifest.Rename the function. Duale AI does not release a reserved name.
RATE_LIMIT_EXCEEDED429One Agent Identity sent registration, heartbeat, or deregistration requests faster than Duale AI accepts. This code carries no retry_after_seconds.Run one Tool Host process per Agent Identity. The SDK stops serve() after three consecutive failed heartbeats, or two consecutive 401 or 403 refusals.
  • Code
    TOO_MANY_TOOLS
    Status
    422
    Meaning
    The manifest declares more Tools than one Tool Host can publish.
    Next action
    Serve fewer Tools from this process, or split them across Tool Hosts under separate Agent Identities.
  • Code
    REQUEST_BODY_TOO_LARGE
    Status
    413
    Meaning
    The registration request is larger than 4 MiB. The same code rejects an oversized Tool Result.
    Next action
    Shorten schemas and descriptions, or split the Tools across Tool Hosts under separate Agent Identities.
  • Code
    TOOL_SCHEMA_TOO_LARGE
    Status
    422
    Meaning
    One @tool input schema is larger than the per-Tool ceiling.
    Next action
    Narrow that Tool’s parameter model and shorten its field descriptions.
  • Code
    RESERVED_TOOL_NAME
    Status
    422
    Meaning
    A @tool function uses a reserved name: exec_agent, finish, list_agents, search_available_tools, context_ls, context_search, context_grep, context_cat, conversation_ls, conversation_search, conversation_grep, or conversation_cat. The Python SDK refuses such a name locally: @tool raises ValueError when it registers the function, before sdk.serve() sends any manifest.
    Next action
    Rename the function. Duale AI does not release a reserved name.
  • Code
    RATE_LIMIT_EXCEEDED
    Status
    429
    Meaning
    One Agent Identity sent registration, heartbeat, or deregistration requests faster than Duale AI accepts. This code carries no retry_after_seconds.
    Next action
    Run one Tool Host process per Agent Identity. The SDK stops serve() after three consecutive failed heartbeats, or two consecutive 401 or 403 refusals.

Automatic retries

Automatic retries depend on the operation:

  • Task stream. Each stream operation has at most five attempts, including the initial attempt. The default HTTP transport retries a received HTTP 500–599, a problem response with retryable=true, or a recoverable interruption such as a lost connection. A received 5xx is retryable even when its problem details omit the flag or set it to false. These causes share one budget, including credential renewal failures within an attempt; receiving events does not reset it. Until Duale AI accepts the stream, the SDK resubmits with the same Task identifier. After that, it reconnects and resumes after the last event it received. A stream that closes without a final event also triggers recovery. An end-to-end encryption failure ends the stream without retrying. A received task.completed, task.error, or task.stopped ends consumption; the SDK does not retry a final task.error, even if its problem details say retryable=true. Recovery uses exponential backoff with random variation, not retry_after_seconds. The attempt limit sets no total duration.
  • Inside the Task. Duale AI retries failing work on its side. The Task still ends with exactly one outcome.
  • SDK Tool callable. Retries are opt-in. Tool retries defines the shared deadline and the risk of repeating an external Effect.
  • Library API. The SDK renews its credential and its encryption key once automatically when either is refused or expires. It does not retry a request rejected by a business rule, or a failed request whose outcome is unknown. wait_for_document() keeps polling while reads succeed and the document is not yet ready or failed. If an error remains after that recovery, polling stops.
  • File upload parts. Each part of a file upload retries network and timeout failures plus HTTP 500, 502, 503, and 504, for at most five attempts.
  • Cached activity. Set @activity(max_retries=...); Activity caching defines the defaults, the timeout, and the retry-safety requirements.

Reconnecting, or resubmitting the same Task identifier, replays only the events Duale AI still keeps. It neither reruns completed work nor recovers an expired final event. There is no separate Task Result lookup, so save the Task ID and its final outcome in your application.

Reuse a task_id only for an exact retry of the same request; task_id states why.

Every Task carries an absolute deadline; ask states the default. A Task that does not finish in time fails with TASK_DEADLINE_EXCEEDED. Deadlines and Stop explains what can still happen after the deadline.

Circuit breaker

The process-local circuit breaker counts retryable failed Task outcomes and DualeAIConnectionError, unless its problem details say retryable=False. Stops, cancellation, other local exceptions, and explicitly non-retryable Task failures do not count. A completed Task resets the failure count.

By default, ten consecutive counted failures open the circuit and block new Task submissions with RuntimeError. After 30 seconds, the SDK allows one Task submission to check for recovery. If it succeeds, the circuit closes. Set BackpressureConfig.failure_threshold and recovery_timeout_seconds to change those defaults; see BackpressureConfig.