home / skills / jeremylongshore / claude-code-plugins-plus-skills / perplexity-reliability-patterns

This skill implements Perplexity reliability patterns such as circuit breakers, idempotency, and bulkheads to improve fault tolerance and resilience.

npx playbooks add skill jeremylongshore/claude-code-plugins-plus-skills --skill perplexity-reliability-patterns

Review the files below or copy the command above to add this skill to your agents.

Files (1)
SKILL.md
7.1 KB
---
name: perplexity-reliability-patterns
description: |
  Implement Perplexity reliability patterns including circuit breakers, idempotency, and graceful degradation.
  Use when building fault-tolerant Perplexity integrations, implementing retry strategies,
  or adding resilience to production Perplexity services.
  Trigger with phrases like "perplexity reliability", "perplexity circuit breaker",
  "perplexity idempotent", "perplexity resilience", "perplexity fallback", "perplexity bulkhead".
allowed-tools: Read, Write, Edit
version: 1.0.0
license: MIT
author: Jeremy Longshore <[email protected]>
---

# Perplexity Reliability Patterns

## Overview
Production-grade reliability patterns for Perplexity integrations.

## Prerequisites
- Understanding of circuit breaker pattern
- opossum or similar library installed
- Queue infrastructure for DLQ
- Caching layer for fallbacks

## Circuit Breaker

```typescript
import CircuitBreaker from 'opossum';

const perplexityBreaker = new CircuitBreaker(
  async (operation: () => Promise<any>) => operation(),
  {
    timeout: 30000,
    errorThresholdPercentage: 50,
    resetTimeout: 30000,
    volumeThreshold: 10,
  }
);

// Events
perplexityBreaker.on('open', () => {
  console.warn('Perplexity circuit OPEN - requests failing fast');
  alertOps('Perplexity circuit breaker opened');
});

perplexityBreaker.on('halfOpen', () => {
  console.info('Perplexity circuit HALF-OPEN - testing recovery');
});

perplexityBreaker.on('close', () => {
  console.info('Perplexity circuit CLOSED - normal operation');
});

// Usage
async function safePerplexityCall<T>(fn: () => Promise<T>): Promise<T> {
  return perplexityBreaker.fire(fn);
}
```

## Idempotency Keys

```typescript
import { v4 as uuidv4 } from 'uuid';
import crypto from 'crypto';

// Generate deterministic idempotency key from input
function generateIdempotencyKey(
  operation: string,
  params: Record<string, any>
): string {
  const data = JSON.stringify({ operation, params });
  return crypto.createHash('sha256').update(data).digest('hex');
}

// Or use random key with storage
class IdempotencyManager {
  private store: Map<string, { key: string; expiresAt: Date }> = new Map();

  getOrCreate(operationId: string): string {
    const existing = this.store.get(operationId);
    if (existing && existing.expiresAt > new Date()) {
      return existing.key;
    }

    const key = uuidv4();
    this.store.set(operationId, {
      key,
      expiresAt: new Date(Date.now() + 24 * 60 * 60 * 1000),
    });
    return key;
  }
}
```

## Bulkhead Pattern

```typescript
import PQueue from 'p-queue';

// Separate queues for different operations
const perplexityQueues = {
  critical: new PQueue({ concurrency: 10 }),
  normal: new PQueue({ concurrency: 5 }),
  bulk: new PQueue({ concurrency: 2 }),
};

async function prioritizedPerplexityCall<T>(
  priority: 'critical' | 'normal' | 'bulk',
  fn: () => Promise<T>
): Promise<T> {
  return perplexityQueues[priority].add(fn);
}

// Usage
await prioritizedPerplexityCall('critical', () =>
  perplexityClient.processPayment(order)
);

await prioritizedPerplexityCall('bulk', () =>
  perplexityClient.syncCatalog(products)
);
```

## Timeout Hierarchy

```typescript
const TIMEOUT_CONFIG = {
  connect: 5000,      // Initial connection
  request: 30000,     // Standard requests
  upload: 120000,     // File uploads
  longPoll: 300000,   // Webhook long-polling
};

async function timedoutPerplexityCall<T>(
  operation: 'connect' | 'request' | 'upload' | 'longPoll',
  fn: () => Promise<T>
): Promise<T> {
  const timeout = TIMEOUT_CONFIG[operation];

  return Promise.race([
    fn(),
    new Promise<never>((_, reject) =>
      setTimeout(() => reject(new Error(`Perplexity ${operation} timeout`)), timeout)
    ),
  ]);
}
```

## Graceful Degradation

```typescript
interface PerplexityFallback {
  enabled: boolean;
  data: any;
  staleness: 'fresh' | 'stale' | 'very_stale';
}

async function withPerplexityFallback<T>(
  fn: () => Promise<T>,
  fallbackFn: () => Promise<T>
): Promise<{ data: T; fallback: boolean }> {
  try {
    const data = await fn();
    // Update cache for future fallback
    await updateFallbackCache(data);
    return { data, fallback: false };
  } catch (error) {
    console.warn('Perplexity failed, using fallback:', error.message);
    const data = await fallbackFn();
    return { data, fallback: true };
  }
}
```

## Dead Letter Queue

```typescript
interface DeadLetterEntry {
  id: string;
  operation: string;
  payload: any;
  error: string;
  attempts: number;
  lastAttempt: Date;
}

class PerplexityDeadLetterQueue {
  private queue: DeadLetterEntry[] = [];

  add(entry: Omit<DeadLetterEntry, 'id' | 'lastAttempt'>): void {
    this.queue.push({
      ...entry,
      id: uuidv4(),
      lastAttempt: new Date(),
    });
  }

  async processOne(): Promise<boolean> {
    const entry = this.queue.shift();
    if (!entry) return false;

    try {
      await perplexityClient[entry.operation](entry.payload);
      console.log(`DLQ: Successfully reprocessed ${entry.id}`);
      return true;
    } catch (error) {
      entry.attempts++;
      entry.lastAttempt = new Date();

      if (entry.attempts < 5) {
        this.queue.push(entry);
      } else {
        console.error(`DLQ: Giving up on ${entry.id} after 5 attempts`);
        await alertOnPermanentFailure(entry);
      }
      return false;
    }
  }
}
```

## Health Check with Degraded State

```typescript
type HealthStatus = 'healthy' | 'degraded' | 'unhealthy';

async function perplexityHealthCheck(): Promise<{
  status: HealthStatus;
  details: Record<string, any>;
}> {
  const checks = {
    api: await checkApiConnectivity(),
    circuitBreaker: perplexityBreaker.stats(),
    dlqSize: deadLetterQueue.size(),
  };

  const status: HealthStatus =
    !checks.api.connected ? 'unhealthy' :
    checks.circuitBreaker.state === 'open' ? 'degraded' :
    checks.dlqSize > 100 ? 'degraded' :
    'healthy';

  return { status, details: checks };
}
```

## Instructions

### Step 1: Implement Circuit Breaker
Wrap Perplexity calls with circuit breaker.

### Step 2: Add Idempotency Keys
Generate deterministic keys for operations.

### Step 3: Configure Bulkheads
Separate queues for different priorities.

### Step 4: Set Up Dead Letter Queue
Handle permanent failures gracefully.

## Output
- Circuit breaker protecting Perplexity calls
- Idempotency preventing duplicates
- Bulkhead isolation implemented
- DLQ for failed operations

## Error Handling
| Issue | Cause | Solution |
|-------|-------|----------|
| Circuit stays open | Threshold too low | Adjust error percentage |
| Duplicate operations | Missing idempotency | Add idempotency key |
| Queue full | Rate too high | Increase concurrency |
| DLQ growing | Persistent failures | Investigate root cause |

## Examples

### Quick Circuit Check
```typescript
const state = perplexityBreaker.stats().state;
console.log('Perplexity circuit:', state);
```

## Resources
- [Circuit Breaker Pattern](https://martinfowler.com/bliki/CircuitBreaker.html)
- [Opossum Documentation](https://nodeshift.dev/opossum/)
- [Perplexity Reliability Guide](https://docs.perplexity.com/reliability)

## Next Steps
For policy enforcement, see `perplexity-policy-guardrails`.

Overview

This skill implements Perplexity reliability patterns for production integrations, including circuit breakers, idempotency, bulkheads, timeouts, graceful degradation, and a dead letter queue. It provides practical building blocks to make Perplexity calls resilient and observable in high-volume services. Use it to prevent cascading failures and reduce duplicate or lost operations.

How this skill works

The skill wraps Perplexity calls with a circuit breaker that trips on error thresholds and exposes open/half-open/close events. It generates deterministic or stored idempotency keys to prevent duplicate processing. Separate queues (bulkheads) manage concurrency by priority. Timeouts enforce operation scopes and fallbacks use cached data when the service fails. Failed operations are recorded to a dead letter queue for retries and alerting.

When to use it

  • Integrating Perplexity AI calls in production APIs or background jobs
  • Protecting downstream systems from Perplexity outages or slow responses
  • Ensuring eventual consistency when retries might cause duplicates
  • Prioritizing critical traffic while limiting resource contention
  • Implementing observability and automated retry workflows for failures

Best practices

  • Tune circuit breaker settings by traffic volume and acceptable failure rate (volumeThreshold, errorThresholdPercentage, resetTimeout)
  • Prefer deterministic idempotency keys derived from operation + params for safe retries; use stored keys when determinism is not possible
  • Segment work with bulkheads by business priority to avoid noisy neighbors
  • Apply a timeout hierarchy (connect, request, upload, longPoll) and fail fast to free resources
  • Use cached fallbacks for degraded modes and mark staleness to inform callers
  • Push permanent failures from DLQ to alerting and incident workflows after a configurable retry limit

Example use cases

  • Protecting real-time user requests to Perplexity with a circuit breaker and fallback cached answer
  • Running bulk synchronizations in low-priority queues while keeping payment processing in a high-priority queue
  • Generating idempotency keys for message handlers to prevent double-charges or duplicate records
  • Recording failed enrichment calls to a dead letter queue for scheduled reprocessing and operator review
  • Exposing health endpoints that report degraded state when the Perplexity circuit is open or DLQ grows

FAQ

How do I choose circuit breaker thresholds?

Start with conservative volume and error thresholds based on baseline error rates, then adjust using observed latency and failure patterns; automate alerts when the breaker state changes.

When should I use deterministic idempotency keys vs random keys?

Use deterministic keys when the same input should map to the same operation result; use stored random keys when operations are initiated client-side or when inputs cannot be canonicalized reliably.