Never hardcode a provider

Checked 22 Sep 2026 · By Luke Czak

ArticleOpinionFree to read

A model id pinned in code is a claim about what will answer, not a fact. Routing every call through a selection layer turns a dead provider from an incident into a config change.

I have had a model provider go unavailable mid-task more than once, and each time the cost was not the outage itself — outages end — it was how much of my code assumed that provider would always be the one answering. The first time it happened, a hardcoded model identifier meant every call site that referenced it broke the same way at the same time, and fixing it meant hunting down every place that string appeared rather than changing one thing in one place. That is not a provider problem. That is an architecture problem I had built myself, by treating a specific vendor’s specific model as a fact about my system rather than as a choice my system happened to be making that day.

The fix is not complicated in principle: put a selection layer between the code that needs "a capable model" and the code that decides which specific model, from which specific provider, actually answers that need right now. The calling code asks for a capability — something that can reason about a long context, something cheap for a mechanical task, something with tool-use support — and the routing layer resolves that into an actual model id based on what is currently available, what is currently rate-limited, and what the budget for this task allows. The calling code never names a vendor. It cannot hardcode what it never sees.

What makes this worth the extra layer, rather than a purity exercise, is what happens on the day a provider actually goes down or a specific model gets deprecated out from under you. With a routing layer, that is a change to the routing layer’s configuration — which providers are live, which model ids are current, how the budget splits — and every caller keeps working without being touched, because none of them ever depended on the removed thing directly. Without one, it is an incident: every call site that named the dead model breaks at once, discovered by errors rather than by a planned migration, usually at the worst possible time because that is when providers tend to have problems.

The part of this I underestimated at first is that model deprecation does not always announce itself. I have had a pinned model id sit in configuration for weeks after the provider had actually stopped serving that model, silently falling back to something else, or simply failing, with nothing in the normal operation of the system flagging that the pin was dead. A pin is a claim about what will answer a request. It is not a fact, and treating it as one — trusting a config value written months ago over checking what a provider is actually serving right now — is how a system keeps running on a model that quietly isn’t the one you think it is. The routing layer earns its keep here too: verify what is live before trusting what is pinned, rather than the other way round.

Provider-agnostic design gets talked about as a hedge against vendor lock-in, and it is that, but the sharper reason to do it is operational rather than strategic. I am not building this because I expect to switch providers wholesale one day for pricing reasons. I am building it because providers already fail in small, specific, un-announced ways on a normal week, and the choice is whether that failure is a five-minute config change or a scramble through every file in the repo that happens to mention a vendor’s name. I would rather spend that five minutes updating a routing table than spend an afternoon working out which of a hundred call sites still assumes yesterday’s vendor is still answering.

Comments (0)

Sign in to comment.