Use case · Multi-tenancy

One agent, a thousand customers, no crossed wires

The demo works because there is one tenant. The fear starts the day there are two: a support assistant that can look up any order, in a product where each customer must only ever see their own. CoreBase makes that boundary something the agent runs inside rather than something it is asked to respect.

Why this breaks in a way normal SaaS doesn't

In an ordinary application, the boundary is a filter in code you wrote, on a path you chose, for a request whose shape you know. With an agent, none of that holds. The caller is a model deciding at runtime which tool to reach for and with what arguments — and the input it is reasoning over includes text a stranger wrote.

So the two things teams try first both fail quietly. Putting the user's ID in the system prompt makes identity a suggestion: anything in the conversation can argue with it, and nothing enforces it downstream. Checking permissions inside each tool means the boundary is now maintained in N places, and the one that matters is whichever one someone forgot when they added a connector last quarter.

The version that holds has one property: it is impossible to express the wrong thing. Not caught — impossible.

Four layers, and none of them is a prompt

01

The tool list is built per request

Not once at startup. Every request assembles the tools that this caller may reach, from their role, the sources you scoped them to, and whether writes are on. A tool that isn't in the list can't be hallucinated into existence, so the most common failure — an agent reaching something nobody meant it to — has no path.

02

Identity travels with the call

For an API, the verified identity is templated into a header the endpoint reads, and any identifying parameter is pinned server-side and hidden from the model. For a database, it is set into the session before the statement runs, so your own row-level security policies filter — your rules stay yours, we only carry the subject.

03

Missing identity means no access

A source scoped to an end user is absent from any request that can't prove one. There is no unscoped fallback to forget about, which is the shape most isolation bugs actually take: not a broken filter, a path where the filter never ran.

04

Your tenants are separated in the database too

Everything CoreBase stores for you — conversations, memory, approvals, audit — sits behind PostgreSQL row-level security with FORCE enabled, so isolation is enforced by the database rather than by every query being written correctly.

What it looks like in your code

Your server says who is asking. Nothing else in the request can contradict it, because the caller never gets to write that field.

a token, scoped to one of your users

const token = cb.widgetToken(user.id, {
  sources: ["orders", "invoices"],   // and nothing else
  limits:  { daily: 50 },
});

From there the agent may only reach those two sources, and inside them only the rows that belong to user.id — because that identity is carried into the request your API sees, or into the database session your own policies read.

The part most systems get wrong

Ask your own stack one question: what does it do for a caller with no identity at all? A cron job, an internal console, a token missing the claim you assumed would be there. If the answer is “returns everything”, you have an unscoped fallback, and something is probably already using it.

Here the answer is that the source is not registered at all. There is no tool, so there is no call, so there is nothing to filter and nothing to get wrong. Isolation you can only lose by deleting a rule — not by forgetting one.

Questions people ask

Isn't tenant isolation just a WHERE clause?

It is, until an LLM is choosing the query. The filter has to hold when the caller is a model that can be argued with, when a tool is reached through a path you didn't picture, and when a colleague adds a data source next quarter without reading your isolation code. That means the boundary can't live in the prompt or in each tool's body — it has to be somewhere every call passes through, and it has to be wrong-by-default rather than right-by-default.

My end users aren't in my identity provider. Does that work?

That's the case this is built for. Your customers' users exist in your database, not in your directory, so there is nothing to do a standard on-behalf-of exchange against. Your server states who is asking — in a signed token for the widget, or as a field on the API request — and that identity is what scopes the tools and reaches the database. The caller can't set it themselves.

What happens if the identity can't be resolved?

The source disappears. Not a fallback to unscoped access, not an empty result set — a source that needs an end-user identity is simply not registered for a request that doesn't carry one, so there is no tool for the model to call. That covers the cases people forget: an internal console, a scheduled job, a token missing the claim you expected.

Can the model be tricked into passing someone else's ID?

Identity parameters are removed from the schema the model receives, and applied over its arguments at execution. The model cannot fill in a field it was never shown, and if it invents one anyway, the pinned value wins. For databases, the verified identity is set into the session so your own row-level security does the filtering — the value never reaches the SQL text.

Do I have to rewrite my agent?

No. You keep your agent and your prompts. CoreBase sits between it and your systems, so what changes is which tools that agent is handed on any given request, and what those tools can reach when they run.

Put it in front of your own users

Connect one source, scope a token to one of your customers, and see what the agent can and can't reach. Free to start, no card.

Start building