Eighty-seven hardcoded model references
The model configuration file at the core of my platform opens with a docstring I wrote as a confession: change models here, changes propagate everywhere, no more hunting down 87 hardcoded references. The number is real. Before I centralized model routing, 87 places in my own codebase named a model directly. Every one of them was a place a swap could fail, and swaps did fail there: a model change would land, and some corner of the system would keep calling the old one because a call site got missed in the hunt.
Everyone in enterprise AI agrees you should design for replacement. Agreement is cheap, and most of what gets written about composability, including the version of this essay I would have produced a year ago, stays safely abstract. So this is the concrete version, written against the platform I operate: an AI analytics product at a large payments company, agents over a semantic layer on live point-of-sale and payments data, converted this quarter into a general agent orchestration platform. Composability is a measurable property. The measurement is how many files you touch to replace a layer.
The model plane: one factory, six providers, zero-deploy swaps
Every LLM call in the platform goes through one factory, keyed by a use case and an invocation key. There are 42 named invocation points registered on the production system: the main chat agent, its intent planner, its synthesis node, the admin agent, the investigation sub-agent, SQL generation, memory extraction, session titling, and so on. Each declares a default. None of them hardcode a model. Resolution is a three-level chain, and the core of it fits on a slide:
def _get_effective_config(use_case: ModelUseCase, invocation_key: str) -> ModelConfig:
# 1. Per-invocation override (set by an admin at runtime)
override = _invocation_overrides.get(invocation_key)
if override:
return override
# 2. Per-use-case override
uc_override = _use_case_overrides.get(use_case.value)
if uc_override:
return uc_override
# 3. Hardcoded default
return MODELS[use_case]The overrides are set from an admin screen and distributed through Redis, which means any agent's model, temperature, or token ceiling can be re-pointed at runtime with no deploy. Behind the factory sit six provider integrations: Anthropic, OpenAI, xAI, OpenRouter, and two local inference servers speaking the OpenAI protocol.
This stopped being theoretical the day Anthropic access went away. The admin agent had been pinned to claude-sonnet-4-6 back when Anthropic was the canonical provider. The comment in the config file records what happened next: with Anthropic offline, the invocation point was re-pointed to the system reasoning default, a locally served open-weight model, so the agent runs end to end without Anthropic credentials. A canonical vendor disappeared and the remediation was a config field. That is the entire composability argument compressed into one incident.
Swappability also runs vertically, by capability tier. Use cases declare intent: a reasoning tier for multi-step analysis, a fast tier for summaries, a nano tier for classification at temperature 0.3, a SQL tier forced to temperature 0.0 because query generation should never be creative, an investigation tier for long-form synthesis. Agents bind to tiers, and tiers bind to models. The admin agent's main node defaults to a premium hosted model with a 128,000-token output ceiling because its reports run long; the intent planner in front of the customer agent runs the nano tier because classification is cheap and constant. Same platform, different economics per node, all of it declared.
Cost lives in this plane too. The system prompt splits into a static section of roughly 3,000 tokens, cached by the provider at a 90% discount, and a dynamic tail of roughly 200. Tools bind in alphabetical order so the cache key stays stable across turns. When people ask where AI margin comes from, most of the honest answer is here, in the architecture, before any vendor negotiation starts.
The data contract: agents never see tables
Below the agents sits a semantic layer, and it is the contract that makes everything above it swappable. Agents query nine business domains: revenue, labor, guest, product, operations, payments, tax compliance, forecasting, and server performance. They do not write SQL against operational tables. The layer supports progressive disclosure, so a summary-level answer costs about 500 tokens and returns in under 50 milliseconds, and an agent only pulls detail when the question demands it. Insights generated from those queries carry provenance objects, so any number an agent asserts can be traced back to the query that produced it. That is the difference between an answer and a claim.
The contract proved itself during the platform conversion. When the runtime went from hardwired agents to compiled manifests in March, the semantic layer did not change; the query tool the agents call is the same tool on both sides of the cutover. Layers with contracts survive re-architecture. Layers without them are the re-architecture. The discipline extends down into the database: an insight's source type must be one of four declared values, enforced as a schema constraint, so a new ingestion path cannot invent a category without a migration that says so out loud.
The contract is also pinned by a golden suite: 76 test cases across 7 categories, from semantic queries to chart rendering to edge cases. A February run scored 24 of 27 semantic queries passing (88.9%) with 92.6% tool-routing accuracy. Those two numbers are my swap insurance. When a model changes underneath the platform, I do not debate whether the new one feels smarter. I re-run 76 cases and read the scoreboard, and the three failures get investigated before anything ships.
Observability is where governance becomes operational
The observability plane is manually instrumented, for an unglamorous reason: the agent framework's bundled callback handler mis-times streaming latency, so the platform creates traces and spans directly against the tracing SDK, with real start and end times per phase: planner, agent, tools. Every trace is stamped with the model provider and model id that actually served it. When a swap lands, it shows up in the traces the same minute, attributed and filterable. Tool executions feed Prometheus histograms with buckets from 10 milliseconds to 10 seconds, per tool, per status.
There is also a standing rule written into the repository's operating instructions: check the traces before modifying agent code. Never guess without evidence. That one sentence has stopped more bad fixes than any review process I have run, because agent bugs lie about their location and traces do not. It changes who can settle an argument, too. When someone asks why an answer was slow, the trace splits the time across planner, agent, and each tool call, and the debate ends. Latency arguments without phase-level spans are astrology.
Governance runs in this plane too, as code on the request path. Gate 1 asks whether this user may compile this agent at all, and fails with an exception before anything is built. Gate 2 intersects the tools an agent's manifest requests with what this specific user is authorized to hold, and logs the count on every compile, authorized over collected. If a manifest overrides a tool that a gate has removed, the compile raises rather than shipping a quietly weaker agent. The policy document still exists. The guardrail is an executable path that fails closed.
The phase progression, as it actually happened
Composability essays, including the earlier draft of this one, tend to describe maturity phases in the abstract: foundation, augmentation, orchestration. Here is mine as a git log instead:
| Date | What happened | The number |
|---|---|---|
| Dec 6 | First commit of the product. One agent, hardwired into the websocket runtime. | 1 agent |
| Feb 4 | Second agent, also hardwired. Every shared fix now lands twice. | 2 agents, 4 runtime branch sites |
| Mar 8 | Manifest platform lands at 10:07. Full compiler cutover at 14:19 the same day. No feature flag, no legacy fallback. | 40/40 contract checks pass |
| Mar 21 | Scale-out: seven new agent manifests in a single commit. | Registry grows 2 to 9 |
| Mar 28 | Post-platform cleanup pass deletes more than it adds. | 104 files, +5,783 / -7,996 lines |
| Apr 3 | The audit: fork the repo, strip the entire restaurant domain in one commit, keep the chassis. | 340 files, -68,555 lines, boots |
I want to be precise about whose failure the starting point was. The monolith was mine. The 87 hardcoded references were lines I wrote or approved, during months when I was reading essays about composability and nodding along. The bill arrived at the fork: separating domain from platform took a 340-file commit that deleted 68,555 lines, and it still needed 1,979 new lines just to stitch seams that should have existed from the first month. I have now paid both prices, and designing for replacement on day one is drastically cheaper than retrofitting it at the fork.
The board math on the model plane alone: a model refresh across 87 call sites is an engineering sprint, every time, and the model market currently moves monthly. Through one registry it is a config change plus a 76-case golden run, an afternoon of work, with a trace proving exactly what changed. Run that delta twelve times a year and the difference is most of an engineer.
One more observation from living that table: the two most important days in it were both single days. The cutover took one day because the legacy path was deleted the same afternoon the platform landed, and the fork took one day because by then the seams were real. The four months in between were ordinary feature work accumulating against clean interfaces. If your composability roadmap contains a phase called transformation that lasts two quarters, the seams are not done, and the roadmap knows it.
The audit is deletion
The composable enterprise argument usually ends in a procurement posture: stay flexible, avoid lock-in, preserve optionality. I think that framing points at the wrong actor. Lock-in gets described as something vendors do to you. Mine was 87 lines of my own code, and no vendor wrote any of them.
So here is the test I trust, and the claim I expect some architects to push back on: fork-and-strip is the only composability audit that means anything. Take your system and ask what it would cost to delete the entire business domain in one commit and have the chassis boot. If the honest answer is a quarter of untangling, what you own is a product wearing a gateway. Five months ago my own system failed this test. In April I ran the audit for real, deleted 68,555 lines, and the chassis came up and compiled agents the same day. Design for replacement, then prove it by deletion. The market will run this audit on you eventually, and it will not schedule it for your convenience.