Skip to main content
Usman Imran
All posts
5 min read

Multi-Tenant or One Instance Per Client?

ArchitectureLoop AgentMulti-Tenancy

The BYD rollout covers 40+ dealership locations. Each one has its own phone numbers, its own calendar, its own inventory, and its own idea of how a lead should be handled. That shape forces a decision before you write much of anything: does every location get its own deployment, or does one system serve all of them?

Per-client instances feel safer. Total isolation, no shared blast radius, and per-location customisation is trivial because nothing is shared. It's also the answer that stops working somewhere around location fifteen.

What isolation actually costs at 40 locations

Forty deployments means forty upgrade paths. A prompt fix, a bug in handoff logic, a provider SDK bump — each one is now a fleet-wide operation rather than a deploy. Forty monitoring targets, forty sets of credentials, forty places for configuration to quietly diverge until two locations behave differently and nobody can say when that started.

The failure mode isn't dramatic. It's that the cost of every change scales linearly with the client count, so shipping improvements gets slower exactly as the deployment gets more successful. That's a bad gradient to be on.

Isolate the data, share the code

The approach I settled on separates two things that per-instance architecture conflates: isolation of data and isolation of code. Tenants genuinely must not see each other's conversations, leads, or calendars — that's non-negotiable. But that isolation belongs at the data layer, not in forty copies of a codebase.

Supabase with row-level security enforces it where it should be enforced: at the database, as policy, rather than as a filter that every query is trusted to remember. A missing WHERE clause becomes a query that returns nothing instead of a cross-tenant leak. Number provisioning through Telnyx maps locations onto the shared system without duplicating it, and Cal.com handles per-location scheduling against each tenant's real availability.

Configuration as data, not as a fork

The piece that makes this hold up under real customisation pressure is modelling each tenant as a squad of specialised agents — reception, sales, follow-up — defined as configuration rather than code. A location that wants different qualification questions or a different handoff threshold gets a different squad definition, not a branch.

That distinction is the whole architecture, really. The moment per-client behaviour lives in code, you've rebuilt per-client instances with extra steps and worse ergonomics. Keeping it as data means a new location is a row and a phone number, not a deployment.

The honest trade

Shared infrastructure means a bad deploy can affect everyone at once, and that risk is real — it's bought back with staged rollout and tenant-scoped feature flags, not wished away. Multi-tenancy is also genuinely harder to get right at the start; RLS policies are unforgiving, and the correct time to discover you've modelled tenancy wrong is not after forty locations depend on it.

There's also a case where per-instance is simply correct: a client with regulatory requirements demanding physical data separation isn't negotiating, and the right answer is a dedicated deployment priced accordingly. I'd treat that as a deliberate exception, not as a reason to make it the default for everyone else.

Working on something similar? I'd be glad to talk through it.

Book a Discovery Call