Skip to content

Models & tiers

You can pick a model two ways: a concrete model string (provider-specific) or an abstract ModelTier that each provider maps to its own model.

Concrete model strings

Pass whatever the backend understands:

from caw import Agent

agent = Agent(provider="claude_code", model="opus")
agent = Agent(provider="codex", model="gpt-5.5")

A concrete string is tied to one provider. In an auto-provider order it is dropped on fallback (the next provider wouldn't recognize it).

Model tiers (portable)

ModelTier expresses intent — "give me the strongest" or "give me the fast/cheap one" — and each provider resolves it to a concrete model:

from caw import Agent, ModelTier

agent = Agent(model=ModelTier.STRONGEST)  # provider picks its best model
agent = Agent(model=ModelTier.FAST)       # provider picks its fast model
Tier Meaning Example (Claude Code) Example (Codex)
ModelTier.STRONGEST Best available model opus provider default
ModelTier.FAST Cheapest / fastest claude-haiku-4-5 gpt-5.3-codex-spark

Because tiers re-resolve per provider, they're the right choice whenever you use a fallback order. See examples/model_tiers.py:

"""Model tiers: use provider-agnostic model selection."""

import os

os.environ["CAW_LOG"] = "full"

from caw import Agent, ModelTier


def main():
    # Use the fast model tier — each provider maps this to its cheapest/fastest model.
    # Claude Code: claude-haiku-4-5-20251001, Codex: gpt-5.3-codex-spark
    agent = Agent(model=ModelTier.FAST, data_dir="caw_data")

    traj = agent.completion("What model are you? Answer in one sentence.")
    print(traj.result)
    print(f"\nmodel: {traj.model}")
    print(f"tokens: {traj.usage.total_tokens}")


if __name__ == "__main__":
    main()

Configuring tier defaults

The model each tier maps to is not hardcoded — it's resolved from config that lives under ~/.caw/ (override the base dir with CAW_HOME). Inspect and edit it with the caw config CLI:

$ caw config list                              # effective model per provider/tier, with source
$ caw config set opencode strongest openai/gpt-5.5-turbo
$ caw config get opencode strongest
$ caw config unset opencode strongest          # revert to the shipped default
$ caw config path                              # where ~/.caw/config.json lives
$ caw config refresh                           # re-fetch the shipped defaults now

caw config set writes to ~/.caw/config.json; only the keys you override are stored, and everything else falls through to the shipped defaults.

For a given provider/tier, the model is resolved in this order (highest first):

  1. The provider env var — ANTHROPIC_MODEL / ANTHROPIC_SMALL_FAST_MODEL, OPENCODE_MODEL / OPENCODE_SMALL_FAST_MODEL
  2. User config ~/.caw/config.json (what caw config set edits)
  3. Remote defaults fetched from CAW_DEFAULTS_URL and cached under ~/.caw/cache/
  4. Baked-in defaults shipped in the wheel (the offline floor)

An explicit model= on the Agent (or CAW_MODEL) bypasses tier resolution entirely.

Updatable shipped defaults

The shipped defaults are served from a JSON file in the repo (caw/defaults/models.json), which is both bundled in the wheel and published at the default CAW_DEFAULTS_URL. caw fetches it lazily and caches it for CAW_DEFAULTS_TTL seconds (default 24h). That means the default models can be updated without cutting a release — edit that file on main and every install picks it up within the TTL (or immediately with caw config refresh). Set CAW_DEFAULTS_URL=off to disable network fetches and pin to the baked-in defaults.

Reasoning effort

Set the reasoning budget with reasoning= ("high", "medium", "low") at construction or later:

agent = Agent(model="opus", reasoning="high")
agent.set_reasoning("medium")

Both model and reasoning have environment-variable fallbacks — CAW_MODEL and CAW_EFFORT — so you can configure them without touching code. See Environment variables.

Asking a sub-task to use a cheaper model

You can also steer the agent itself to use a cheaper model for exploratory sub-steps, as in examples/haiku.py:

import os

os.environ["CAW_LOG"] = "full"

from caw import Agent

if __name__ == "__main__":
    agent = Agent(
        system_prompt="You are a software engineer.",
        data_dir="caw_data",
    )

    with agent.start_session() as session:
        session.send(
            "Can you explore the codebase at caw/auth and tell me what it does in five sentences? If possible, do the exploration use Haiku model"
        )