Skip to content

Error Handling

Every error the SDK throws extends AgentSDKError (code, message, optional cause), so you can catch broadly or narrow to a specific type:

ErrorThrown when
ProviderErrora model provider's HTTP call failed
ProviderTimeoutErrora provider call exceeded its timeoutMs
ToolExecutionErrora tool's execute threw
ToolValidationErrortool input failed Zod validation
GuardrailErroran input/output guardrail returned pass: false
ApprovalRequiredErrora tool needs approval but no approvalHandler was supplied
MaxTurnsExceededErrorthe loop hit agent.maxTurns without a final answer
HandoffLoopErrorhandoff chain exceeded maxHandoffDepth or cycled
HandoffNotFoundError(reserved) referenced handoff target doesn't exist
StructuredOutputErrorfinal output didn't match outputSchema after repair attempts
SessionStoreErrora SessionStore/LongTermMemory read or write failed
ConfigurationErroran Agent/provider was misconfigured (missing name, missing API key, etc)
ts
import { AgentSDKError, GuardrailError, MaxTurnsExceededError } from "archadi-agent";

try {
  await runner.run(agent, userInput);
} catch (err) {
  if (err instanceof GuardrailError) {
    // input/output was rejected — safe to show err.message to the user
  } else if (err instanceof MaxTurnsExceededError) {
    // the agent got stuck in a tool-calling loop — log and alert
  } else if (err instanceof AgentSDKError) {
    // catch-all for anything else the SDK threw
  } else {
    throw err; // unexpected — don't swallow it
  }
}

Safe failure by design

  • A tool throwing never crashes the run — it's caught, wrapped, and fed back to the model as a tool error so it can retry or explain the failure.
  • A provider failing retries with backoff, then falls through the fallback chain, before the run ultimately fails.
  • Every failure still produces a Trace (status: "failed", with the error recorded) and a run_failed event — nothing fails silently.
  • API keys are read only from environment variables / explicit config objects passed by the host app; the SDK never logs them and only ever sends them in the Authorization/x-api-key header of the relevant provider's request.

Cancelling a run with AbortSignal

Pass a standard AbortSignal to cancel an in-flight run. The signal is threaded through to the underlying provider HTTP calls, so the actual network request is cancelled — not just ignored:

ts
const controller = new AbortController();

// Cancel after 10 seconds
setTimeout(() => controller.abort(), 10_000);

try {
  await runner.run(agent, "Write a very long essay...", {
    signal: controller.signal,
  });
} catch (err) {
  if (err instanceof ProviderError && err.message.includes("aborted")) {
    console.log("Run was cancelled");
  }
}

The signal is also available inside tool implementations via ctx.signal, so long-running tools can cooperatively check for cancellation.