Models¶
Overview¶
The models subsystem decides which model runs a task, how it
authenticates, and what it costs. It is the cost-optimization core:
every task is classified into a tier (CHEAP, CAPABLE, PREMIUM),
each tier maps to a concrete model in a registry, and an auth strategy
chooses between your Claude subscription and the Anthropic API per call.
It owns three concerns: the registry (models, tiers, pricing), the
router (task → tier → model selection, including adaptive routing
from telemetry), and auth/provider configuration (subscription vs
API, persisted to ~/.attune/).
It is not responsible for executing workflows, tracking cross-session telemetry beyond routing stats, or managing memory — those live in their own subsystems. You touch the models layer when you need to know why a task picked a given model, change provider/auth, or read pricing for a cost estimate.
Two CLI namespaces front this subsystem: attune auth (authentication
strategy) and attune provider (provider selection). The Python API
under attune.models is the programmatic equivalent.
Concepts¶
Three ideas compose the whole subsystem:
- Tiers —
ModelTieris a three-value enum (CHEAP,CAPABLE,PREMIUM). Every task type maps to exactly one tier viaTASK_TIER_MAP; unknown tasks default toCAPABLE. - Registry —
MODEL_REGISTRYis a nesteddict[str, dict[str, ModelInfo]]keyed by provider then tier. EachModelInfocarries the modelid, per-million input/output cost, and capability flags.get_model(provider, tier)resolves one. - Auth strategy —
AuthStrategydecides, per module, whether to run on your subscription or the API.AuthModeisSUBSCRIPTION,API, orAUTO(size-based). The strategy is persisted at~/.attune/auth_strategy.json(AUTH_STRATEGY_FILE).
The three tiers¶
| Tier | Enum value | Use for |
|---|---|---|
CHEAP |
"cheap" |
summarize, classify, triage, lint, format, simple Q&A |
CAPABLE |
"capable" |
generate code, fix bugs, review security, write tests, refactor |
PREMIUM |
"premium" |
coordinate, synthesize, architectural decisions, final review |
get_tier_for_task(task_type) returns the ModelTier for a task
string or TaskType; get_tasks_for_tier(tier) lists the task strings
in a tier.
Core data structures¶
| Type | What it represents |
|---|---|
ModelInfo |
One model: id, provider, tier, input_cost_per_million, output_cost_per_million, max_tokens, supports_vision, supports_tools. Convenience properties cost_per_1k_input / cost_per_1k_output / model_id / name are read-only. |
AuthStrategy |
Auth configuration: subscription_tier, default_mode, the small/medium LOC thresholds, and setup_completed. Note setup_completed is a plain field, not a method. |
LLMResponse |
A completed call: content, model_id, provider, tier, tokens_input, tokens_output, cost_estimate, latency_ms. Aliased properties cost, total_tokens, success, input_tokens, output_tokens are read-only. |
ProviderConfig |
Provider selection: mode (ProviderMode.SINGLE), primary_provider, available_providers, cost_optimization. |
How auth resolves a mode¶
AuthStrategy.get_recommended_mode(module_lines) resolves by
subscription tier first, not by size. If default_mode is not
AUTO, it returns that. In AUTO, the PRO and API_ONLY tiers
always return AuthMode.API (pay-per-token is more economical there)
and module size is never consulted. Only MAX / ENTERPRISE tiers use
the size thresholds: modules under small_module_threshold (500) — and
medium modules under medium_module_threshold (2000) when
prefer_subscription is set — favor the subscription; larger ones
favor the API for its 1M context window. The zero-config default tier
is PRO, so out of the box AUTO returns API regardless of size.
estimate_cost(module_lines, mode) returns the projected cost for a
given mode so you can compare before committing.
Design & extension¶
Design decisions¶
- Single provider, tiered models.
ModelProviderhas one member (ANTHROPIC) andProviderMode.HYBRIDis retained only for backward compatibility. The abstraction is kept so adding a provider later is a registry change, not an architecture change, but the supported surface today is Anthropic-only —get_modelenforces it. - Tasks classify into tiers, not models. Routing maps a task to a
ModelTier(TASK_TIER_MAP), and the tier maps to a concrete model separately. This decouples "how hard is this task" from "which model is current," so a model swap is a registry edit with no routing changes. - Auth strategy persists to a file, not env.
AuthStrategysaves to~/.attune/auth_strategy.jsonso the choice survives across sessions and is inspectable.AUTOkeeps the decision adaptive (size-based) rather than hard-coding a mode. - Adaptive routing degrades gracefully.
AdaptiveModelRouterrequiresMIN_SAMPLE_SIZEobservations before it overrides the static tier choice, so a cold system behaves identically to static routing.
Extension points¶
- Add a task type: add a
TaskTypemember and its.valueto the appropriate tier set (CHEAP_TASKS,CAPABLE_TASKS, orPREMIUM_TASKS).TASK_TIER_MAPis derived from those sets, soget_tier_for_taskandget_tasks_for_tierpick it up automatically. - Add or re-price a model: edit
MODEL_REGISTRY;get_model,get_all_models, andget_pricing_for_modelreflect it with no other changes. - Plug in a custom executor: implement the
LLMExecutorprotocol (async run,get_model_for_task,estimate_cost).MockLLMExecutoris the reference implementation. - Tune adaptive routing: pass
max_cost,max_latency_ms, ormin_success_ratetoAdaptiveModelRouter.get_best_model, or readget_routing_statsto drive your own selection logic.