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.

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
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

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 five 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

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.