Provider Flow

Focus Deep-Dive

How OpenClaw resolves, probes, and selects provider authentication at runtime

When a user invokes a model command or an agent needs to make an LLM call, OpenClaw must decide which provider to use and how to authenticate. The provider flow pipeline resolves configuration, builds a list of probe targets from profiles, environment variables, and models.json, executes concurrent auth probes against each candidate, and returns a ranked summary. This module is the backbone of multi-provider credential management.

📚 What is this & why does it matter?

Provider flow is the authentication resolution pipeline that sits between user-facing model commands and the actual LLM API calls. Every time OpenClaw needs to verify a credential, rotate between providers, or report which auth sources are active, the provider flow module orchestrates the process. Understanding this pipeline is essential for debugging auth failures, configuring multi-provider setups, and extending the credential system with new provider types.

💡 Real-world analogy

Imagine an airport security checkpoint with multiple lanes — biometric, boarding pass, and priority access. Each traveler (API request) is checked against available lanes (providers) based on their ticket type (auth source). The system probes each lane for availability and wait time, then routes the traveler to the best open lane. If biometric is down, it falls back to boarding pass. The provider flow is that checkpoint controller — it probes every lane in parallel, records latency and status, and returns the fastest healthy option.

Contents

Overview

Primary role

Resolve provider credentials from multiple sources (profiles, env vars, models.json) and probe each candidate to determine auth readiness.

Main inputs

OpenClawConfig, a list of provider names, model candidates, and AuthProbeOptions controlling timeout and concurrency.

Key decisions

Which auth sources to include, how to order probes, which candidates pass validation, and how to handle rate-limited or expired credentials.

Outputs / side effects

An AuthProbeSummary containing per-target results with status codes, latency measurements, and reason codes for failures. No mutations to config.

flowchart TD
  CFG["OpenClawConfig"]
  RESOLVE["resolveModelsAuthContext()"]
  PROVIDERS["Provider list"]
  MODELS["Model candidates"]
  BUILD["buildProbeTargets()"]
  TARGETS["AuthProbeTarget[]"]
  EARLY["Early-exit results"]
  PROBE["runAuthProbes()"]
  CONCURRENT["Concurrent HTTP probes"]
  COLLECT["Collect results + latency"]
  SUMMARY["AuthProbeSummary"]
  ORDER["Auth order filtering"]
  DISPLAY["Display to user / route to handler"]

  CFG --> RESOLVE
  RESOLVE --> PROVIDERS
  RESOLVE --> MODELS
  PROVIDERS --> BUILD
  MODELS --> BUILD
  BUILD --> TARGETS
  BUILD --> EARLY
  TARGETS --> PROBE
  PROBE --> CONCURRENT
  CONCURRENT --> COLLECT
  COLLECT --> SUMMARY
  EARLY --> SUMMARY
  SUMMARY --> ORDER
  ORDER --> DISPLAY

The diagram shows the full provider auth pipeline. Configuration enters at the top and is resolved into provider and model lists. The buildProbeTargets function generates concrete probe targets (some may be excluded early with reason codes). The remaining targets are probed concurrently, and results are collected into an AuthProbeSummary. Auth order commands can then filter or re-rank the results before they are displayed or used for routing.

Core Types

The provider flow module revolves around two key type families: probe results that capture the outcome of individual auth checks, and provider overviews that summarize the effective credential state for display commands.

AuthProbeResult & AuthProbeStatus

Each probe target produces an AuthProbeResult with a status enum indicating whether the credential is valid, rate-limited, expired, or otherwise unusable. The reasonCode field provides machine-readable detail for failures.

// src/commands/models/auth.ts
export type AuthProbeStatus =
  | "ok"
  | "auth"
  | "rate_limit"
  | "billing"
  | "timeout"
  | "format"
  | "unknown"
  | "no_model";

export type AuthProbeReasonCode =
  | "excluded_by_auth_order"
  | "missing_credential"
  | "expired"
  | "invalid_expires"
  | "unresolved_ref"
  | "ineligible_profile"
  | "no_model";

export type AuthProbeResult = {
  provider: string;
  model?: string;
  profileId?: string;
  label: string;
  source: "profile" | "env" | "models.json";
  mode?: string;
  status: AuthProbeStatus;
  reasonCode?: AuthProbeReasonCode;
  error?: string;
  latencyMs?: number;
};
StatusMeaningTypical cause
okCredential is valid and the model respondedSuccessful lightweight probe request
authAuthentication failed (401/403)Invalid or revoked API key / token
rate_limitProvider returned 429Too many requests; credential is valid but throttled
billingBilling or quota errorAccount has exceeded spend limits
timeoutProbe did not respond in timeNetwork issue or slow provider endpoint
formatResponse was not valid JSON/expected schemaProvider returned HTML error page or unexpected format
unknownUnclassified failureCatch-all for unexpected HTTP status codes
no_modelThe requested model is not available on this providerModel name mismatch or model not deployed

AuthProbeSummary

The summary type wraps all individual results with timing metadata. The durationMs field measures wall-clock time for the entire probe batch, including concurrency overhead.

// src/commands/models/auth.ts
export type AuthProbeSummary = {
  startedAt: number;
  finishedAt: number;
  durationMs: number;
  totalTargets: number;
  options: AuthProbeOptions;
  results: AuthProbeResult[];
};

ProviderAuthOverview

Used by the models list display layer, this type summarizes the effective auth configuration for a single provider without performing any live probes.

// src/commands/models/list.types.ts
export type ProviderAuthOverview = {
  provider: string;
  effective: {
    kind: "profiles" | "env" | "models.json" | "missing";
    detail: string;
  };
  profiles: {
    count: number;
    oauth: number;
    token: number;
    apiKey: number;
    labels: string[];
  };
  env?: { value: string; source: string };
  modelsJson?: { value: string; source: string };
};

Decision Points

DecisionWhat controls itWhy it matters
Auth source prioritysource field on AuthProbeResult — profiles take precedence over env, which takes precedence over models.jsonEnsures user-configured profiles override implicit environment credentials
Auth order exclusionmodelsAuthOrderGetCommand retrieves a per-provider ordering; targets not in the order list receive excluded_by_auth_orderAllows users to pin specific credentials for specific providers without removing others
Probe concurrencyAuthProbeOptions controls max parallel probes and per-probe timeoutPrevents overwhelming providers with simultaneous requests while keeping total latency low
Early-exit vs live probebuildProbeTargets can mark targets as early-exit (e.g. missing_credential, expired) without issuing an HTTP requestSaves network round-trips for credentials that are known-invalid from config alone
Model candidate filteringThe modelCandidates array intersected with provider-supported modelsAvoids probing providers that cannot serve any of the requested models

How It Works

The provider flow pipeline operates in four distinct phases: context resolution, target building, concurrent probing, and order management. Each phase is implemented as a separate async function, enabling callers to compose only the phases they need.

Phase 1 — Auth Context Resolution

Before any probing can occur, the pipeline must resolve the current configuration into a structured auth context. The resolveModelsAuthContext function loads and validates the config, resolves the default agent, and discovers all available plugin providers.

// src/commands/models/auth.ts
async function resolveModelsAuthContext(): Promise<ResolvedModelsAuthContext> {
  const config = await loadValidConfigOrThrow();
  const defaultAgentId = resolveDefaultAgentId(config);
  const agentDir = resolveAgentDir(config, defaultAgentId);
  const workspaceDir =
    resolveAgentWorkspaceDir(config, defaultAgentId) ??
    resolveDefaultAgentWorkspaceDir();
  const providers = resolvePluginProviders({
    config,
    workspaceDir,
    // ... additional options
  });
  return { config, agentDir, workspaceDir, providers };
}
Key insight: The auth context is resolved once and passed to all downstream functions. This ensures that the config snapshot is consistent across the entire probe cycle — no TOCTOU races between reading profiles and executing probes.

Phase 2 — Probe Target Building

The buildProbeTargets function takes the resolved providers and model candidates and produces two outputs: a list of AuthProbeTarget objects for live probing and a list of AuthProbeResult objects for targets that were excluded early.

// src/commands/models/auth.ts
export async function buildProbeTargets(params: {
  cfg: OpenClawConfig;
  providers: string[];
  modelCandidates: string[];
  options: AuthProbeOptions;
}): Promise<{
  targets: AuthProbeTarget[];
  results: AuthProbeResult[];
}>
Early-exit results: Targets with missing_credential, expired, invalid_expires, unresolved_ref, or ineligible_profile reason codes are returned in the results array without ever hitting the network. This is a critical performance optimization for setups with many configured but inactive providers.

Enumerate credential sources. For each provider in the providers array, the function collects credentials from three sources: auth profiles (OAuth tokens, API keys, bearer tokens), environment variables (OPENAI_API_KEY, etc.), and models.json entries. Each source produces one or more candidate targets.

Cross-product with model candidates. Each credential source is paired with each model candidate to produce the full target matrix. If a provider declares supported models, only the intersection is kept.

Apply early-exit filters. Targets with missing, expired, or invalid credentials are immediately converted to AuthProbeResult entries with the appropriate reasonCode and removed from the live-probe list.

Apply auth order filter. If the user has set an auth order for this provider (via modelsAuthOrderSetCommand), targets not in the order list are excluded with excluded_by_auth_order. Remaining targets are reordered to match the user-specified sequence.

Return split results. The function returns { targets, results } where targets are ready for live probing and results contain all early-exit entries.

Phase 3 — Concurrent Auth Probing

The runAuthProbes function takes the targets from Phase 2 and executes lightweight HTTP requests against each provider endpoint in parallel. It reports progress via an optional callback and returns the complete AuthProbeSummary.

// src/commands/models/auth.ts
export async function runAuthProbes(params: {
  cfg: OpenClawConfig;
  providers: string[];
  modelCandidates: string[];
  options: AuthProbeOptions;
  onProgress?: (update: {
    completed: number;
    total: number;
    label?: string;
  }) => void;
}): Promise<AuthProbeSummary>
sequenceDiagram
    participant Caller as Model Command
    participant Run as runAuthProbes()
    participant Build as buildProbeTargets()
    participant P1 as Provider A Probe
    participant P2 as Provider B Probe
    participant P3 as Provider C Probe
    participant Summary as AuthProbeSummary

    Caller->>Run: providers, modelCandidates, options
    Run->>Build: cfg, providers, modelCandidates
    Build-->>Run: targets[A,B,C] + earlyExits[D]
    par Concurrent probes
        Run->>P1: HTTP HEAD /v1/models (API key)
        Run->>P2: HTTP HEAD /v1/models (OAuth token)
        Run->>P3: HTTP HEAD /v1/models (env key)
    end
    P1-->>Run: 200 OK (45ms)
    P2-->>Run: 401 Unauthorized (32ms)
    P3-->>Run: 429 Rate Limited (120ms)
    Run->>Run: onProgress({ completed: 3, total: 3 })
    Run->>Summary: merge earlyExits + probe results
    Run-->>Caller: AuthProbeSummary

The sequence diagram shows how runAuthProbes orchestrates the full pipeline. It first calls buildProbeTargets to get the live targets and early-exit results. The live targets are probed concurrently (respecting the concurrency limit in AuthProbeOptions). Each probe returns an HTTP status code and latency measurement. The progress callback fires after each probe completes. Finally, early-exit results and live probe results are merged into a single AuthProbeSummary.

Phase 4 — Auth Order Management

Users can control which credentials are used for a given provider via three order management commands. These commands read and write a per-provider auth order that is respected by buildProbeTargets.

// src/commands/models/auth-order.ts
export async function modelsAuthOrderGetCommand(
  opts: { provider: string; agent?: string; json?: boolean },
  runtime: RuntimeEnv,
)

export async function modelsAuthOrderSetCommand(
  opts: { provider: string; agent?: string; order: string[] },
  runtime: RuntimeEnv,
)

export async function modelsAuthOrderClearCommand(
  opts: { provider: string; agent?: string },
  runtime: RuntimeEnv,
)
CommandEffectUse case
auth-order getReads the current auth order for a provider; outputs JSON or human-readable listInspecting which credential labels are active and in what priority
auth-order setWrites a new ordered list of credential labels for a providerPinning a specific OAuth profile over an API key for production use
auth-order clearRemoves any custom ordering, reverting to the default source-priority resolutionResetting after debugging or switching between agent configurations

Edge Cases

ScenarioBehaviorResultRationale
All probes timeoutEvery target returns status: "timeout"Summary has zero "ok" results; caller surfaces a connectivity warningPrevents silent fallback to a broken provider
No providers configuredbuildProbeTargets returns empty targets and empty resultsSummary with totalTargets: 0; caller prompts setup instructionsGraceful handling of fresh installations
Expired OAuth tokenEarly-exit with reasonCode: "expired"No HTTP request is made; result is included in summaryAvoids wasting a probe on a credential that config alone shows is invalid
Rate-limited but validProbe returns status: "rate_limit" with valid latencyCredential is considered valid but deprioritized in rankingA 429 means the credential works — the provider is just busy
Auth order excludes allAll targets receive excluded_by_auth_orderNo live probes run; summary contains only early-exit resultsUser may have misconfigured the order; clear command is the fix
Duplicate credential sourcesSame API key in both env var and profileBoth appear as separate targets; both are probedDeduplication is intentionally not performed — allows users to see redundancy
Model not on providerProvider does not list the model in supported setEarly-exit with reasonCode: "no_model"Prevents a guaranteed 404 from reaching the network

Configuration

SettingLocationDefaultDescription
Probe timeoutAuthProbeOptions.timeoutMs5000Maximum milliseconds to wait for a single probe response
Concurrency limitAuthProbeOptions.concurrency5Maximum number of simultaneous HTTP probe requests
Provider listparams.providers (resolved from config)All configuredWhich providers to include in the probe sweep
Model candidatesparams.modelCandidatesAll models in configWhich models to cross-product with provider credentials
Auth orderPer-provider stored setting (managed via auth-order commands)None (use source priority)Custom ordering of credential labels within a single provider
Default agentOpenClawConfig.defaultAgentIdFirst agentWhich agent context to use when resolving workspace and provider directories
Model Picker

Uses probe results to select the optimal provider/model combination for a given request.

Search Setup

Provider configuration discovery and workspace search path resolution.

Command Parsing

How slash commands like /models auth-order set are parsed before reaching the auth order handlers.

Source Files

Key files involved in provider flow
src/
├── commands/
│   └── models/
│       ├── auth.ts                    ← AuthProbeResult, buildProbeTargets(), runAuthProbes()
│       ├── auth-order.ts              ← Auth order get/set/clear commands
│       ├── list.types.ts              ← ProviderAuthOverview display type
│       └── list.ts                    ← Models list command (uses ProviderAuthOverview)
├── config/
│   ├── loader.ts                      ← loadValidConfigOrThrow()
│   └── agents.ts                      ← resolveDefaultAgentId(), resolveAgentDir()
└── plugins/
    └── providers.ts                   ← resolvePluginProviders()