Agents/Providers — Token Management

Focus Deep-Dive

API key rotation, auth health monitoring & cooldown tracking

The token management system handles the lifecycle of API credentials across all providers. It stores multiple auth profiles per provider, monitors their health status, implements fair rotation with cooldown tracking, and synchronizes credentials from external CLI tools on a configurable TTL.

Key Rotation Health Monitoring Cooldown Tracking File Locking

📚 What is this & why does it matter?

When you have multiple API keys for the same provider (personal key, team key, trial key), this system decides which one to use for each request. It tracks which keys are healthy, which hit rate limits, and when to retry failed keys — all without manual intervention.

💡 Real-world analogy

Imagine a delivery company with multiple drivers (API keys). A dispatcher (the auth resolver) assigns packages to drivers based on availability. If a driver gets a flat tire (rate limit), they’re put on a cooldown — no new packages until the tire is fixed. Meanwhile, the dispatcher routes packages to other drivers. If all drivers are down, the dispatcher checks who will be back soonest and queues the next package for that time. The dispatcher also periodically checks in with a staffing agency (external CLI sync) to see if new drivers have been added.

Contents

Overview

Primary role

Select the best available API credential for each provider request, avoiding rate-limited or expired keys.

Main inputs

Auth store file (JSON with file-locking), provider ID from model resolution, API error responses (401/403/429).

Key decisions

Which auth profile to select (recency + random rotation), when to enter/exit cooldown, whether to allow a cooldown probe.

Outputs / side effects

Selected API credentials for the request, updated cooldown state in the auth store, health status metadata for reporting.

flowchart TD
  subgraph Input["Request Phase"]
    REQ["Agent request\n(provider + model)"]
    STORE["auth-profiles.ts\nLoad auth store\n(file-locked)"]
  end
  subgraph Resolution["Profile Resolution"]
    ORDER["resolveAuthProfileOrder()"]
    FILTER["Filter expired &\ncooldown profiles"]
    SORT["Sort by recency\n+ random for fairness"]
  end
  subgraph Health["Health Check"]
    HEALTH["AuthProfileHealth\nok | expiring | expired\nmissing | static"]
    COOLDOWN["Cooldown tracker\nnow + TTL expiry"]
    SOONEST["getSoonestCooldownExpiry()"]
  end
  subgraph Output["Selected Credential"]
    CRED["API key / token\nfor provider request"]
    FAIL["markAuthProfileFailure()\nSet cooldown on error"]
  end
  REQ --> STORE
  STORE --> ORDER
  ORDER --> FILTER
  FILTER --> SORT
  SORT --> HEALTH
  HEALTH --> CRED
  CRED -->|"401/403/429"| FAIL
  FAIL --> COOLDOWN
  COOLDOWN --> SOONEST
  SOONEST -->|"retry after expiry"| FILTER

The flow begins when the agent runtime needs credentials for a provider request. The auth store is loaded with file locking to prevent corruption from concurrent access. resolveAuthProfileOrder() applies configured ordering, filters out expired and cooling-down profiles, then sorts by recency with random jitter for fair rotation across equivalent profiles.

Decision Points

DecisionWhat controls itWhy it matters
Profile selection orderConfig ordering + recency + random jitterEnsures fair distribution across keys to avoid burning one key’s rate limit
Enter cooldownAPI error status codes: 401, 403, 429Prevents repeated requests with a known-bad credential
Cooldown TTLnow + ttl (varies by error type)Rate limits need shorter cooldown than auth failures
Allow cooldown probeshouldAllowCooldownProbeForReason()Lets recently-failed profiles retry once with short timeout to detect recovery
External CLI syncEXTERNAL_CLI_SYNC_TTL_MS (15 min)Picks up new credentials from CLI tools without restart
Lock acquisitionAUTH_STORE_LOCK_OPTIONS.retries (10)Prevents data corruption from concurrent agent processes

How It Works

Token management operates in three phases: resolution (selecting the best profile), monitoring (tracking health status), and recovery (cooldown management after failures).

Phase 1: Auth Profile Resolution

The resolveAuthProfileOrder() function implements the core selection algorithm. It loads the auth store, applies any configured ordering preferences, filters out unusable profiles, and returns the best available credential.

// auth-profiles.ts — simplified resolution flow
export async function resolveAuthProfileOrder(
  providerId: string,
  configOrder?: string[],
): Promise<ResolvedAuthProfile | null> {
  // 1. Load auth store with file locking
  const store = await loadAuthStore({
    retries: AUTH_STORE_LOCK_OPTIONS.retries,       // 10
    minTimeout: 100,                                 // ms
    maxTimeout: 10_000,                              // ms
    factor: 2,                                       // exponential backoff
    stale: 30_000,                                   // 30s staleness
  });

  // 2. Get all profiles for this provider
  const profiles = store.profiles.filter(p => p.providerId === providerId);

  // 3. Apply config ordering (if provided)
  const ordered = configOrder
    ? reorderByConfig(profiles, configOrder)
    : profiles;

  // 4. Filter expired and in-cooldown profiles
  const now = Date.now();
  const available = ordered.filter(p => {
    if (p.expiresAt && p.expiresAt < now) return false;       // expired
    if (p.cooldownExpiry && p.cooldownExpiry > now) return false; // in cooldown
    return true;
  });

  // 5. Sort by recency with random jitter for fair rotation
  available.sort((a, b) => {
    const recencyDiff = (b.lastUsedAt ?? 0) - (a.lastUsedAt ?? 0);
    return recencyDiff + (Math.random() - 0.5) * 1000;
  });

  return available[0] ?? null;
}
Key detail: The random jitter in step 5 prevents deterministic key selection. Without it, the same key would always be chosen first after cooldown recovery, creating an uneven load distribution.

Phase 2: Health Monitoring

The AuthProfileHealth type tracks the health status of each stored credential. The health check runs before every profile selection and after every API response.

StatusMeaningTransition trigger
okProfile is valid and readySuccessful API call or cooldown expiry
expiringProfile will expire soonexpiresAt - now < warningThreshold
expiredProfile has expiredexpiresAt < now
missingNo profiles found for providerEmpty profile list after filtering
staticProfile has no expiry (permanent key)Profile loaded without expiresAt field

Phase 3: Cooldown & Recovery

When an API request fails with an auth or rate-limit error, markAuthProfileFailure() places the profile into cooldown. The cooldown expiry is calculated as now + ttl where the TTL varies by error type.

Auth Profile State Machine

stateDiagram-v2
  [*] --> Valid : Profile loaded
  Valid --> InUse : Selected for request
  InUse --> Valid : API success
  InUse --> Cooldown : API error (401/403/429)
  Cooldown --> Probe : shouldAllowCooldownProbe()
  Probe --> Valid : Probe succeeds
  Probe --> Cooldown : Probe fails
  Cooldown --> Valid : Cooldown expiry passes
  Valid --> Expiring : Near expiry threshold
  Expiring --> Expired : expiresAt reached
  Expired --> Valid : Manual refresh / CLI sync
  Expired --> [*] : Removed from store

  note right of Cooldown
    cooldownExpiry = now + ttl
    ttl varies by error type:
    401/403 → longer TTL
    429 → shorter TTL
  end note

  note right of Probe
    Single retry with short timeout
    Only allowed for specific error reasons
  end note

The state machine above shows the full lifecycle of an auth profile. Most profiles spend their time in the Valid ↔ InUse cycle. The Cooldown state is entered on API failure and exited either by timer expiry or by a successful cooldown probe. The probe mechanism is controlled by shouldAllowCooldownProbeForReason() which only allows probes for transient errors (rate limits), not permanent failures (invalid key).

All profiles in cooldown: When every profile for a provider is in cooldown, the system calls getSoonestCooldownExpiry() to find the earliest available retry time. The caller can then decide whether to wait or fail immediately with timing information.

Annotated Code Walkthrough

Walk through the cooldown marking and recovery flow.

When an API call fails, markAuthProfileFailure() is called with the error details:

// auth-profiles.ts — marking a failure
export async function markAuthProfileFailure(
  profileId: string,
  error: ProviderError,
): Promise<void> {
  const store = await loadAuthStore(AUTH_STORE_LOCK_OPTIONS);
  const profile = store.profiles.find(p => p.id === profileId);
  if (!profile) return;

  const ttl = getCooldownTTL(error.status);
  profile.cooldownExpiry = Date.now() + ttl;
  profile.lastError = {
    status: error.status,
    message: error.message,
    timestamp: Date.now(),
  };

  await saveAuthStore(store);
}

function getCooldownTTL(status: number): number {
  switch (status) {
    case 429: return 30_000;   // Rate limit: 30s cooldown
    case 401: return 300_000;  // Unauthorized: 5min cooldown
    case 403: return 600_000;  // Forbidden: 10min cooldown
    default:  return 60_000;   // Other: 1min cooldown
  }
}

The getSoonestCooldownExpiry() function scans all profiles to find the earliest recovery time:

// auth-profiles.ts — finding nearest available retry
export function getSoonestCooldownExpiry(
  profiles: AuthProfile[],
): number | null {
  const now = Date.now();
  let soonest: number | null = null;

  for (const p of profiles) {
    if (!p.cooldownExpiry || p.cooldownExpiry <= now) {
      return null; // At least one profile is already available
    }
    if (soonest === null || p.cooldownExpiry < soonest) {
      soonest = p.cooldownExpiry;
    }
  }

  return soonest;
}
Early exit optimization: If any profile has no cooldown or an expired cooldown, the function returns null immediately — indicating at least one profile is available right now without waiting.

External CLI credential synchronization runs on a 15-minute TTL to pick up new keys without restarting the agent:

// auth-profiles.ts — external CLI sync
const EXTERNAL_CLI_SYNC_TTL_MS = 900_000; // 15 minutes
let lastExternalSync = 0;

async function maybeSyncExternalCredentials(
  store: AuthStore,
): Promise<boolean> {
  const now = Date.now();
  if (now - lastExternalSync < EXTERNAL_CLI_SYNC_TTL_MS) {
    return false; // TTL not yet expired
  }

  lastExternalSync = now;
  const externalProfiles = await loadFromExternalCLI();

  let changed = false;
  for (const ext of externalProfiles) {
    const existing = store.profiles.find(
      p => p.providerId === ext.providerId && p.keyHash === ext.keyHash,
    );
    if (!existing) {
      store.profiles.push(ext);
      changed = true;
    }
  }

  return changed;
}

File Locking Details

The auth store is a JSON file on disk, and multiple agent processes may attempt to read or write it concurrently. The file-locking mechanism uses exponential backoff with configurable retry parameters:

ParameterValuePurpose
retries10Maximum lock acquisition attempts
factor2Exponential backoff multiplier
minTimeout100 msInitial retry delay
maxTimeout10,000 msMaximum retry delay cap
stale30,000 msLock is considered stale after 30s (auto-cleanup)
Stale lock files: If a process crashes while holding the lock, the lock file remains on disk. The stale parameter (30s) ensures that orphaned locks are automatically broken, preventing permanent deadlocks. However, if a legitimate write takes longer than 30s (extremely rare), the stale detection may cause data corruption.

Edge Cases & Error Handling

ScenarioBehaviorCode Location
All profiles in cooldowngetSoonestCooldownExpiry() returns nearest retry timestampauth-profiles.ts
Concurrent store writesFile lock with 10 retries and exponential backoffauth-profiles.ts
Stale lock file from crashAuto-broken after 30s staleness thresholdAUTH_STORE_LOCK_OPTIONS.stale
No profiles for providerHealth status set to missing; fallback to environment variableauth-health.ts
External CLI not availableSync silently skipped; existing profiles usedloadFromExternalCLI()
Profile expires mid-requestCompletion runs normally; next request sees expired statusresolveAuthProfileOrder()
Rate limit with retry-after headerCooldown TTL uses retry-after value if present (clamped to max)markAuthProfileFailure()

Configuration

Knob / ParameterDefaultDescription
EXTERNAL_CLI_SYNC_TTL_MS900000 (15 min)How often to check external CLI tools for new credentials
AUTH_STORE_LOCK_OPTIONS.retries10Maximum lock acquisition attempts with exponential backoff
AUTH_STORE_LOCK_OPTIONS.stale30000 (30s)Threshold after which orphaned lock files are auto-broken
Cooldown TTL (429)30000 (30s)Cooldown duration after rate limit error
Cooldown TTL (401)300000 (5 min)Cooldown duration after unauthorized error
Cooldown TTL (403)600000 (10 min)Cooldown duration after forbidden error
Config orderundefinedOptional user-configured profile priority ordering

See also:

Directory Map

Source files for token management
pi-embedded-runner/
├── auth-profiles.ts             ← Auth store management, profile resolution, file locking
├── auth-health.ts               ← AuthProfileHealth status types and health checks
├── model.ts                     ← Model registry (consumes auth profiles for provider auth)
└── run/
    ├── setup.ts                 ← Passes resolved auth to stream wrappers
    └── attempt.ts               ← Triggers markAuthProfileFailure() on API errors