Skip to content

Auto-provider mode

Don't want to hard-code one provider? Give caw a fallback order and let it use whatever is available at runtime. caw selects the first installed provider and, on the first send, transparently moves to the next one if that provider fails (CLI missing, auth expired) or is rate-limited — no exception handling or provider-picking on your side.

import caw
from caw import Agent

caw.set_provider_order(["claude", "codex", "opencode"])  # set once, globally

agent = Agent(provider="auto")            # uses the global order
traj = agent.completion("Explain this repo")
print(f"[{traj.agent}] {traj.result}")    # whichever provider handled it

Where the order comes from

In priority order (highest first):

Agent(provider=["claude", "codex"])              # explicit per-agent order
caw.set_provider_order([...])                     # global default, used by provider="auto"
os.environ["CAW_PROVIDER"] = "claude,codex,opencode"  # env var, comma list

A single name (provider="claude") stays pinned — no fallback. Use a list or "auto" to opt into fallback.

How fallback works

  1. Selection is a fast, no-network check: caw picks the first provider in the order whose CLI binary is installed. This is what agent.provider reports.
  2. On the first send(), if the chosen provider raises (missing CLI, auth error) or reports a usage limit, caw silently builds the next provider's session and retries.
  3. Once a provider produces the first turn, the session is committed to it. Conversation context can't move across CLIs mid-stream, so any later failure propagates normally.

Prefer a ModelTier in auto mode

Use a ModelTier (or no model) rather than a concrete model string. Tiers are re-resolved per provider, so model selection stays portable across the fallback. A bare concrete model string is dropped when falling back to a different provider — it would be meaningless to the others.

A model per provider in the order

To pin a specific model to each provider in the order, attach it to set_provider_order — as (name, model) tuples or a models= mapping. Each value may be a concrete string or a ModelTier. Because the model is bound to its provider, it is honored even when that provider is reached as a fallback (unlike a bare Agent-level string):

import caw
from caw import Agent, ModelTier

caw.set_provider_order([
    ("claude", ModelTier.STRONGEST),   # re-resolved via the claude tier config
    ("codex", "gpt-5.5"),              # concrete, bound to codex
    ("opencode", "openai/gpt-5.5"),
])
# Equivalent: caw.set_provider_order(["claude", "codex"], models={"codex": "gpt-5.5"})

caw.get_provider_models()             # {'claude': <ModelTier.STRONGEST>, 'codex': 'gpt-5.5', ...}

Agent(provider="auto")                # each provider uses its attached model

A provider's order-model applies only when the Agent sets no model of its own — an explicit model= (or CAW_MODEL) on the Agent always wins.

Inspecting the selection

Pair auto-provider with check_providers() to see what's installed before you commit:

from caw import Agent, check_providers

for h in check_providers(["claude", "codex", "opencode"]):
    print("✓" if h.installed else "✗", h.provider)

print("selected:", Agent(provider="auto").provider.name)

Full example

examples/auto_provider.py:

"""Auto-provider mode: "use whatever provider is available right now".

Set a fallback order once (globally or per-agent). caw selects the first
*installed* provider and, on the first send, transparently moves to the next
one if that provider fails (CLI missing, auth expired) or is rate-limited —
your code never has to catch an exception or pick a provider by hand.

The order can come from (highest priority first):

* an explicit list on the Agent: ``Agent(provider=["claude", "codex"])``
* the global setting: ``caw.set_provider_order([...])`` with ``provider="auto"``
* the ``CAW_PROVIDER`` env var as a comma list: ``CAW_PROVIDER="claude,codex"``

Tip: in auto mode use a ``ModelTier`` (or no model) rather than a concrete model
string — tiers are re-resolved per provider, so model selection stays portable
across the fallback. A bare concrete model string is dropped when falling back to
a different provider (it would be meaningless to the others). If you want a
specific model *per provider* that survives fallback, attach it to the order
itself with ``set_provider_order([(name, model), ...])`` (see below).
"""

import caw
from caw import Agent, ModelTier, check_providers


def main():
    # --- Global order, used by provider="auto" (or by Agent() with no provider). ---
    caw.set_provider_order(["opencode", "codex", "claude"])

    agent = Agent(provider="auto", model=ModelTier.STRONGEST)

    # Selection is a fast, no-network check — the first installed provider wins.
    print("Installed providers, in order:")
    for h in check_providers(["claude", "codex", "opencode"]):
        mark = "✓" if h.installed else "✗"
        print(f"  [{mark}] {h.provider}")
    print(f"\nAuto-selected provider: {agent.provider.name}\n")

    # Just use it. If the selected provider errors or is rate-limited on the
    # first send, caw silently falls back to the next installed one in the order.
    traj = agent.completion("Reply with a one-line hello and include your model name and agent harness name.")
    print(f"Provider used: {traj.agent}")
    print(f"Agent reply: {traj.result}")

    # --- Per-agent order (overrides the global setting). ---
    other = Agent(provider=["codex", "claude"])
    print(f"\nPer-agent order selected: {other.provider.name}")

    # --- Per-provider models in the order. ---
    # Attach a model to each provider: a ModelTier (re-resolved per provider) or a
    # concrete string. Unlike a bare Agent-level model string, these are bound to
    # their provider, so each is used even when reached as a fallback. Applied only
    # when the Agent sets no model of its own.
    caw.set_provider_order([("opencode", "openai/gpt-5.5"), ("codex", ModelTier.STRONGEST), ("claude", "opus")])
    pinned = Agent(provider="auto")
    print(f"Per-provider models: {caw.get_provider_models()}")
    print(f"Pinned-order selected: {pinned.provider.name}")


if __name__ == "__main__":
    main()