Skip to content

Error Recovery

Handle errors gracefully — automatic recovery, error events, and production error-handling patterns.

Error categories

Errors in a CompositeVoice agent fall into three broad categories:

Provider errors occur when a provider fails to initialize, connect, or respond. Examples include invalid API keys, missing peer dependencies, rate limiting, and service outages. These surface as transcription.error, llm.error, or tts.error events depending on which pipeline stage failed.

Network errors occur when a WebSocket connection drops, an HTTP request times out, or a proxy endpoint is unreachable. WebSocket providers (DeepgramSTT, DeepgramTTS, AssemblyAISTT, ElevenLabsTTS, CartesiaTTS) are particularly susceptible. The SDK’s reconnection system handles many of these automatically.

Audio errors occur when microphone access is denied, an audio device disconnects mid-session, or AudioContext playback fails. These surface as audio.capture.error or audio.playback.error events.

The autoRecover config option

Set autoRecover: true on the top-level config to let the SDK attempt automatic recovery from provider errors instead of propagating them immediately. When enabled, the SDK reinitializes crashed providers and resumes the pipeline where possible.

import { CompositeVoice, NativeSTT, AnthropicLLM, NativeTTS } from '@lukeocodes/composite-voice';

const agent = new CompositeVoice({
  stt: new NativeSTT({ language: 'en-US' }),
  llm: new AnthropicLLM({
    proxyUrl: `${window.location.origin}/proxy/anthropic`,
    model: 'claude-haiku-4-5-20251001',
    systemPrompt: 'You are a helpful voice assistant.',
    maxTokens: 200,
  }),
  tts: new NativeTTS(),
  autoRecover: true,
});

When autoRecover is false (the default), every error transitions the agent to the error state and waits for you to call startListening() to retry manually.

Error events

The SDK emits typed error events at each pipeline stage. Every error event includes an error object and a recoverable flag.

agent.error

Emitted for top-level agent errors that are not specific to a single provider. The context field identifies where the error originated.

agent.on('agent.error', (event) => {
  console.error(`[${event.context}] ${event.error.message}`);
  console.log('Recoverable:', event.recoverable);
});

Payload:

FieldTypeDescription
errorErrorThe error that occurred
recoverablebooleanWhether the SDK can attempt recovery
contextstring (optional)Where the error originated (e.g., 'initialize', 'processLLM', 'startListening')

transcription.error

Emitted when the STT provider encounters an error during transcription.

agent.on('transcription.error', (event) => {
  console.error('STT error:', event.error.message);
});

Payload:

FieldTypeDescription
errorErrorThe STT error
recoverablebooleanWhether the transcription can be restarted

llm.error

Emitted when the LLM provider fails during generation. Common causes include rate limiting, invalid model names, and proxy connectivity issues.

agent.on('llm.error', (event) => {
  console.error('LLM error:', event.error.message);
});

Payload:

FieldTypeDescription
errorErrorThe LLM error
recoverablebooleanWhether the generation can be retried

tts.error

Emitted when the TTS provider fails during synthesis or playback.

agent.on('tts.error', (event) => {
  console.error('TTS error:', event.error.message);
});

Payload:

FieldTypeDescription
errorErrorThe TTS error
recoverablebooleanWhether synthesis can be retried

audio.capture.error

Emitted when microphone capture fails. Common causes include device disconnection, permission revocation, and AudioContext issues.

agent.on('audio.capture.error', (event) => {
  console.error('Capture error:', event.error.message);
});

Payload:

FieldTypeDescription
errorErrorThe capture error

audio.playback.error

Emitted when audio playback fails. Common causes include AudioContext suspension, buffer underruns, and browser autoplay policy violations.

agent.on('audio.playback.error', (event) => {
  console.error('Playback error:', event.error.message);
});

Payload:

FieldTypeDescription
errorErrorThe playback error

The CompositeVoiceError base class

All SDK-thrown errors extend CompositeVoiceError, which adds structured metadata to the standard Error class:

import { CompositeVoiceError } from '@lukeocodes/composite-voice';

agent.on('agent.error', (event) => {
  if (event.error instanceof CompositeVoiceError) {
    console.log('Code:', event.error.code);             // e.g., 'PROVIDER_CONNECTION_ERROR'
    console.log('Recoverable:', event.error.recoverable); // true or false
    console.log('Context:', event.error.context);         // optional diagnostic data
  }
});

Properties:

PropertyTypeDescription
codestringMachine-readable error code (e.g., 'PROVIDER_INIT_ERROR', 'TIMEOUT_ERROR')
recoverablebooleanWhether the error can be retried programmatically
contextRecord<string, unknown> (optional)Structured diagnostic data (provider name, status code, etc.)

Error subclasses and their codes:

ClassCodeRecoverableTypical cause
ProviderInitializationErrorPROVIDER_INIT_ERRORNoMissing API key, missing peer dependency
ProviderConnectionErrorPROVIDER_CONNECTION_ERRORYesNetwork issue, service unavailability
ProviderResponseErrorPROVIDER_RESPONSE_ERRORYesHTTP error from provider (e.g., 429 rate limit)
AudioCaptureErrorAUDIO_CAPTURE_ERRORYesDevice disconnected, stream interrupted
AudioPlaybackErrorAUDIO_PLAYBACK_ERRORYesAudioContext failure, decoding error
MicrophonePermissionErrorMICROPHONE_PERMISSION_DENIEDNoUser denied microphone access
ConfigurationErrorCONFIGURATION_ERRORNoInvalid SDK configuration
InvalidStateErrorINVALID_STATE_ERRORNoOperation attempted in wrong state
TimeoutErrorTIMEOUT_ERRORYesWebSocket connection or API call timed out
WebSocketErrorWEBSOCKET_ERRORYesWebSocket connection or send failure

Recoverable vs. non-recoverable errors

The recoverable flag tells you whether it makes sense to retry:

Recoverable errors (recoverable: true) are transient. The user does not need to change anything — a retry may succeed. Examples: network timeouts, WebSocket disconnections, rate-limited API responses. With autoRecover: true, the SDK handles these automatically. Without it, you can call agent.startListening() to restart the pipeline.

Non-recoverable errors (recoverable: false) require user intervention or a configuration change before retrying will help. Examples: missing API keys, denied microphone permissions, invalid configuration. Surface these to the user with a clear message and instructions.

agent.on('agent.error', (event) => {
  if (event.error instanceof CompositeVoiceError) {
    if (event.error.recoverable) {
      showNotification('Temporary issue. Retrying...');
      // The SDK retries automatically when autoRecover is true.
      // Without autoRecover, call agent.startListening() after a delay.
    } else {
      showFatalError(event.error.message);
      // Guide the user: check permissions, verify API keys, etc.
    }
  }
});

Detecting recovery

The agent state machine transitions to error when an error occurs and transitions back to a normal state (e.g., listening) when recovery succeeds. Watch for this transition to update your UI:

agent.on('agent.stateChange', (event) => {
  if (event.previousState === 'error' && event.state !== 'error') {
    showNotification('Recovered successfully');
  }
});

Best practices for production

Subscribe to all error events. Do not rely on agent.error alone. Provider-specific errors (llm.error, tts.error, transcription.error) fire before the top-level agent.error, giving you the chance to log granular diagnostics.

const errorEvents = [
  'agent.error',
  'transcription.error',
  'llm.error',
  'tts.error',
  'audio.capture.error',
  'audio.playback.error',
] as const;

for (const event of errorEvents) {
  agent.on(event, (e) => {
    logToService({ event, error: e.error.message, timestamp: e.timestamp });
  });
}

Enable logging in development. Set logging.level to 'debug' during development to see internal SDK decisions around error recovery and state transitions:

const agent = new CompositeVoice({
  stt, llm, tts,
  autoRecover: true,
  logging: { enabled: true, level: 'debug' },
});

Combine autoRecover with a custom logger. Route SDK logs into your observability stack so you can trace errors in production:

const agent = new CompositeVoice({
  stt, llm, tts,
  autoRecover: true,
  logging: {
    enabled: true,
    level: 'warn',
    logger: (level, message, ...args) => {
      myLogger.log({ level, message, data: args });
    },
  },
});

Handle microphone permission denial at initialization. The MicrophonePermissionError is the most common non-recoverable error. Catch it early and show a clear prompt:

import { MicrophonePermissionError } from '@lukeocodes/composite-voice';

try {
  await agent.initialize();
  await agent.startListening();
} catch (error) {
  if (error instanceof MicrophonePermissionError) {
    showPermissionDialog('Please allow microphone access to use voice features.');
  }
}

Track error counts. A burst of recoverable errors can indicate a systemic issue (e.g., a misconfigured proxy). Set a threshold and surface a warning when it is exceeded:

let errorCount = 0;
const ERROR_THRESHOLD = 5;
const WINDOW_MS = 60_000;

agent.on('agent.error', () => {
  errorCount++;
  setTimeout(() => errorCount--, WINDOW_MS);

  if (errorCount >= ERROR_THRESHOLD) {
    showWarning('Multiple errors detected. Check your network connection.');
  }
});

Full configuration example

import {
  CompositeVoice,
  DeepgramSTT,
  AnthropicLLM,
  DeepgramTTS,
  CompositeVoiceError,
  MicrophonePermissionError,
} from '@lukeocodes/composite-voice';

const agent = new CompositeVoice({
  stt: new DeepgramSTT({
    proxyUrl: `${window.location.origin}/proxy/deepgram`,
    options: { model: 'nova-3', interimResults: true },
  }),
  llm: new AnthropicLLM({
    proxyUrl: `${window.location.origin}/proxy/anthropic`,
    model: 'claude-haiku-4-5-20251001',
    systemPrompt: 'You are a helpful voice assistant. Keep responses brief.',
    maxTokens: 200,
  }),
  tts: new DeepgramTTS({
    proxyUrl: `${window.location.origin}/proxy/deepgram`,
    options: { model: 'aura-2-thalia-en', encoding: 'linear16', sampleRate: 24000 },
  }),
  autoRecover: true,
  reconnection: {
    enabled: true,
    maxAttempts: 5,
    initialDelay: 1000,
    maxDelay: 30000,
    backoffMultiplier: 2,
  },
  logging: { enabled: true, level: 'warn' },
});

// Provider-specific error handlers
agent.on('transcription.error', (e) => {
  console.error('[STT]', e.error.message);
});

agent.on('llm.error', (e) => {
  console.error('[LLM]', e.error.message);
});

agent.on('tts.error', (e) => {
  console.error('[TTS]', e.error.message);
});

agent.on('audio.capture.error', (e) => {
  console.error('[Capture]', e.error.message);
});

agent.on('audio.playback.error', (e) => {
  console.error('[Playback]', e.error.message);
});

// Top-level error handler
agent.on('agent.error', (e) => {
  if (e.error instanceof CompositeVoiceError && !e.error.recoverable) {
    showFatalError(e.error.message);
  }
});

// Recovery detection
agent.on('agent.stateChange', (e) => {
  if (e.previousState === 'error' && e.state !== 'error') {
    showNotification('Connection restored');
  }
});

try {
  await agent.initialize();
  await agent.startListening();
} catch (error) {
  if (error instanceof MicrophonePermissionError) {
    showPermissionDialog('Microphone access is required.');
  } else {
    showFatalError((error as Error).message);
  }
}

See the Events reference for the full event catalog and Example 04 (Error Recovery) for a runnable demo with error simulation.

© 2026 CompositeVoice. All rights reserved.

Font size
Contrast
Motion
Transparency