Error reference

Every public error case, by type. For how the layers fit together — what ends a turn, what the model handles itself — see when it fails.

AgentSessionError

Thrown by send() and generate(). A turn that fails after your message is accepted keeps everything already appended — your message, plus any tool exchanges from completed provider round trips — appends nothing after the failure, and rolls back its undo transaction (tool side-effects revert; history is not transactional). Pre-flight failures — alreadyRunning, toolChoiceUnsupported, unreadableImageAttachment, the tool-choice invalidConfiguration shapes, and StructuredOutputError.unsupported — throw before any session mutation, as does reasoningEffortUnsupported — on both send and generate.

Case When it happens What to do
alreadyRunning send() or generate() while a turn was in flight — one turn at a time per session gate sends on isRunning, or cancel() first
maxToolCallsExceeded the turn hit AgentRunLimits.maxToolCallsPerTurn raise the limit, or scope domains so the model wanders less — see run limits
maxRoundTripsExceeded the turn hit AgentRunLimits.maxProviderRoundTrips raise the limit, or simplify the task
wallClockTimeoutExceeded the turn ran past AgentRunLimits.maxWallClockSeconds, including time inside tool executors raise the limit, or make slow tools faster
cancelled cancel() was called, or the Task running the turn was cancelled expected — partial text stays in currentText
invalidConfiguration(String) a contradictory setup, caught before anything ran (e.g. .required tool choice with no tools, or a tool budget of zero) read the message; fix the configuration
providerEventContractViolation(detail:) a provider that runs tools itself emitted a malformed tool event stream a provider bug — no assistant or tool history persists beyond the already-appended user message; report it to the provider author
toolChoiceUnsupported a non-.auto tool choice was sent to a provider without native tool-choice control check capabilities.supportsToolChoice before forcing — see tool choice
reasoningEffortUnsupported a reasoningEffort was passed to send or generate on a provider without native reasoning-budget control — anything but Apple FM / Private Cloud Compute on OS 27 this cut; thrown on BOTH paths, before any session mutation check capabilities.supportsReasoningEffort first — see Apple Foundation Models
toolChoiceNotHonored(requested:observedToolCalls:) the provider's response violated the requested tool choice — .required with zero calls, or .none with at least one the turn rolled back; branch on requested and retry or relax the demand
egressRefused(detail:) your pre-transmit filter threw — the request never reached the provider detail is your filter's own message; surface your policy to the user
unreadableImageAttachment(url:detail:) a file-URL image passed to send could not be loaded, or no media type was derivable from its extension thrown before any session mutation — fix the file or pass bytes directly, see images
unreadableDocumentAttachment(url:detail:) a file-URL document passed to send(_:documents:) could not be loaded (missing/unreadable, or security-scoped access denied) thrown before any session mutation — fix the file or pass bytes directly, see documents
documentInputUnsupported a NEW document on this send, but the resolved route explicitly does not accept documents (documentInput is false) send to a document-capable provider or tier — see documents
documentHistoryUnsupported the conversation history already carries a document the resolved route can't represent — a seeded conversation, or a continuation after the route resolved to no-documents start a fresh conversation or continue on a document-capable route; the SDK fails loud rather than silently dropping the document
unsupportedDocumentMediaType(mimeType:) a document's media type is outside the v1 allowlist (only application/pdf) send a PDF
documentTooLarge(bytes:limit:) a document, or the turn's aggregate, exceeds the route's decoded-size cap send a smaller or single document — see documents
documentNotPDF the bytes do not begin with the %PDF- signature the file is not a valid PDF regardless of its name
unreadableAudioAttachment(url:detail:) a file-URL audio clip passed to send(_:audio:) could not be loaded thrown before any session mutation; fix the file or pass bytes directly, see audio
audioRouteUnsupported(provider:model:mediaType:) audio was attached on a direct (non-cloud) route, which has no audio input audio is cloud-only; send through AgentKit Cloud, or opt into degradeUnsupportedAttachmentsToText
audioInputUnsupported a cloud route whose audio_input is false, or could not be positively confirmed (audio is fail-closed) send to an audio-capable tier; unlike documents, an unconfirmed route never dispatches audio
audioHistoryUnsupported the conversation history already carries an audio clip the resolved route cannot represent start a fresh conversation or continue on an audio-capable route
unsupportedAudioMediaType(mimeType:advertised:) the clip's media type is not sendable on the route: either not in advertised, or SDK-unknown with the relay-validated opt-in off send a type the route advertises (inspect whether advertised contains mimeType), or set allowRelayValidatedUnknownAudioTypes
audioOffProfile(detail:) a WAV clip is outside the proven profile (not PCM-int, not 16-bit, more than 2 channels, or an off-allowlist sample rate) re-encode to 16-bit PCM, mono or stereo, at 16/44.1/48 kHz
audioNotWAV audio bytes are not a RIFF/WAVE container the file is not a valid WAV regardless of its name or declared type
audioTooLarge(bytes:limit:) a single clip's decoded size exceeds the per-clip cap send a shorter or more compressed clip
totalMediaTooLarge(bytes:limit:) the request's total decoded media (image + document + audio, across history and this turn) exceeds the cross-media cap trim earlier attachments or split the conversation
tooManyAudioBlocks(count:limit:) the outgoing request has more than AudioRef.maxAudioBlocksPerRequest audio clips across conversation history and this turn (so even a text-only send over enough seeded history can trip it) send fewer clips, or continue on a fresh conversation
audioCapabilityContractViolation(detail:) the route advertised a broken audio capability (audio_input disagreeing with its media-type list) a relay bug; report it (the SDK fails loud rather than guess)

send() can also throw SchemaValidationError(failures:) when the provider rejects a tool schema outright — each failure names the tool and the reason. Warnings, by contrast, are non-fatal and land in lastSchemaWarnings — see watch it work.

AgentKitRuntimeError

Thrown by AgentKitRuntime.register(_:) and makeAgent(...).

Case When it happens What to do
duplicateDomain(String) a domain with that id is already registered register each domain once
duplicateTool(String) a tool with that id is already registered, in any domain tool ids are global — rename one
reservedDomainId(String) the domain id collides with the SDK's reserved agentkit namespace pick another id
reservedToolId(String) a tool id starts with the reserved agentkit. prefix pick another id
registryFrozen not reachable through AgentKitRuntime — agents snapshot the registry at creation if you drive a ToolRegistry directly, register everything before handing it to a session
unknownDomains(Set<String>) makeAgent(domains: .only([...])) named ids that are not registered the set carries the unknown ids — register them or fix the scope

StructuredOutputError

Thrown by generate(). A structured request never silently degrades to free text — see structured output.

Case When it happens What to do
unsupported the provider does not advertise supportsStructuredOutput — rejected before any network call pick a provider that does, or fall back to prose
unexpectedToolCall(event:) the provider emitted a tool event during a structured turn a provider bug; event names the offending event
notJSON(rawPreview:) the model's output was not parseable JSON rawPreview is truncated to 256 characters; retry, or loosen the prompt
schemaValidationFailed([SchemaViolation]) valid JSON that violated the requested schema each violation carries a path; tighten the schema description or retry
decodeFailed(String) schema-valid JSON that your Decodable type could not decode — a type/schema mismatch on your side the message is deterministic and names the path; align the type with the schema

BackendRouterError

Thrown by the backend-router and AgentKit Cloud providers. Switch on retryGuidance rather than memorizing this table — see when it fails. metadata carries the server's structured details; safeDescription is safe to display; unsafeMessage is the server's prose, for logs.

These six originate in the SDK itself — the request never produced a server error envelope:

Case When it happens What to do
invalidRequest the request could not be built: blank credentials, a non-positive token budget, or an attempt to override a protected header fix the configuration
httpError(statusCode:) a self-hosted backend returned a non-200 without a parseable error body check the backend's logs
crossOriginRedirectBlocked(message:) the transport refused to follow a redirect to a different origin — following it would leak credentials and the request body point the endpoint at its final origin
contractVersionMismatch(serverVersion:sdkVersion:) the server announced a contract major this SDK was not built against update the SDK (or the server)
signingFailed(message:) the request signer failed, timed out, or returned invalid headers — the request never left the device check your signer; the default timeout is 10 seconds
structuredOutputDialectViolation(code:pointer:message:) a cloud-profile generate() schema fell outside the portable subset every backing model shares — rejected before the request left the device; code names the rule and pointer locates the offending part of the schema shape the schema to the portable subset (object root, every property required and made nullable for optionals, string-only enums, bounded nesting) — see get data, not prose

The rest map one-to-one from the cloud's error codes:

Case When it happens What to do
authInvalid publishable-key problems, or no verifiable user token was presented at all fix the embedded credentials
authExpired the user token's expiry passed refresh the token and retry — guidance is .refreshAuthThenRetry
authUserInvalid a token was presented but failed verification: bad signature, untrusted issuer, wrong audience requiresUserAction — the end user must re-authenticate
accountSuspended the developer's cloud account is suspended operator-resolved; the end user cannot fix it
accountNotFound the publishable key's owning account is missing — a provisioning fault contact support
agentInvalid unknown agentId, or the agent profile is disabled fix the profile name or enable the profile
toolInvalid an active tool name is not enabled on the agent profile align the app's domains with the profile's tool list
quotaExceeded the plan's usage quota window is exhausted (daily turns, monthly tokens) backoff guidance — retry after the window resets
rateLimited a throughput limit tripped honor retryGuidance's backoff
budgetExceeded the app owner's monetary budget is exhausted operator-resolved — raise the budget server-side
entitlementRequired the end user lacks a required entitlement requiresUserAction — offer the upgrade
duplicateRequest a request id was replayed only possible when you pin your own request id — mint a fresh one
modelUnavailable the profile's model is transiently unavailable backoff and retry
cloudInvalidRequest the server rejected the request shape: malformed envelope, contract mismatch, or a tier not configured for the agent a configuration fault — fix it; retrying loops forever
providerError the upstream LLM provider errored after the stream opened backoff and retry
providerUnavailable the cloud could not reach the upstream LLM provider backoff and retry
serviceUnavailable the cloud service itself is unavailable backoff and retry
internalError a cloud-internal fault backoff and retry
streamTruncated the stream died mid-turn without a clean terminal event the turn failed; already-appended history remains, nothing is appended after, undo rolls back — retry
unknown(code:message:metadata:) an error code this SDK version does not know show safeDescription; update the SDK

RetryGuidance

Derived statically from the case — only the backoff hint is dynamic.

Case Meaning
none retrying will not help; check requiresUserAction
refreshAuthThenRetry mint a fresh user token, then retry
retryWithBackoff(afterSeconds:) wait, then retry — afterSeconds carries the server's hint, or a 1-second floor

AnthropicError, OpenAIError, and GeminiError

The direct providers — .anthropic(apiKey:), .openAI(apiKey:), .gemini(apiKey:) — throw their own typed errors when a request cannot be built or the vendor API returns a non-success status. They surface through send() and generate() like any other thrown error.

Case When it happens What to do
invalidRequest the request could not be built (e.g. a blank API key) fix the configuration before sending
httpError(statusCode:) the vendor API returned a non-2xx status inspect the status — back off on 429/5xx, fix the request on 4xx
unsupportedStructuredOutputSchema OpenAI only: a generate() schema used a non-object root, which OpenAI's json_schema rejects give the schema an object root, or use a provider whose structured output accepts it — see get data, not prose

ToolRegistryError

Thrown only when you drive a ToolRegistry directly. Through AgentKitRuntime these surface as AgentKitRuntimeError (above) instead — agents snapshot the registry at creation, so frozen never reaches you on the runtime path.

Case When it happens What to do
duplicateToolId(String) a tool id was registered twice — ids are global across domains rename one
duplicateDomainId(String) a domain id was registered twice register each domain once
unknownTool(String) a lookup named a tool that was never registered register it, or fix the id
unknownDomain(String) a lookup named a domain that was never registered register it, or fix the id
reservedDomainId(String) the domain id collides with the SDK's reserved agentkit namespace pick another id
reservedToolId(String) a tool id starts with the reserved agentkit. prefix pick another id
frozen a write after the registry was snapshotted into a session register everything before handing the registry to a session

AppAttestRegistrarError and AppAttestSignerError

App Attest onboarding throws its own typed vocabulary: register() (and register(reusingKeyID:)) throw AppAttestRegistrarError, and the per-request AppAttestSigner throws AppAttestSignerError. Both fail closed — a tier that requires attestation never silently degrades. For the flow, see attest the device.

Case Type When it happens What to do
unsupportedDevice both App Attest is unavailable — simulator or unsupported hardware fall back to a non-attested path, or tell the user the device is unsupported
keyGenerationFailed(String) registrar generateKey() failed before any network I/O surface the message and retry
challengeFailed(String) both the GET /v1/attest/challenge handshake failed or returned no nonce back off and retry
attestationFailed(String) registrar Apple-side attestKey failed retry; persistent failure means the device cannot attest
registrationRejected(statusCode:code:message:) registrar the register endpoint rejected the request — code is the wire error code when present branch on code; fix the request or credentials
malformedResponse(String) registrar a 200 whose body was not the ratified {"key_id": …} shape, or a mismatched echo treat as a server fault and retry
crossOriginRedirectBlocked(String) registrar the register POST was answered with a cross-origin redirect — refused fail-closed point the endpoint at its final origin

AppleModelError and AppleProviderError

The on-device and Private Cloud Compute providers throw their own typed vocabulary: AppleModelError for model failures — refusals, guardrail violations, context overflow, quota exhaustion — and AppleProviderError for configuration errors caught before any model work. Both catalogs live with their provider: see on-device with Apple.