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.