Guide4 min read

Multi-Tenant AI Agents: One Agent Per Customer, Isolated by Default

Multi-tenant AI agents give every customer their own isolated agent. Here's the operator, tenant-user, and Account Kit model behind per-user isolation.

Read the docs →

Guide
TL;DR
  • A multi-tenant AI agent gives each of your customers their own agent, isolated from every other customer's.
  • The model is Operator → Account Kit → tenant user → resources: you're the operator, each customer is a tenant user.
  • A tenant user isn't an auth subject it never signs in; it's an opaque record you manage via API, SDK, or CLI.
  • Every resource cards, inboxes, vault, connections, sessions — belongs to exactly one tenant user.
  • In code, `users.create()` per signup and `naive.forUser(id)` scopes every call to that customer.
  • Isolation is structural, not a prompt instruction one customer's scoped client cannot reach another's data.

If you're putting AI agents into a product with more than one customer, the central requirement is isolation: customer A's agent must never touch customer B's data, money, or credentials. Multi-tenant AI agents solve this by giving each customer their own agent, isolated by default — not one shared agent with a system prompt that politely asks it to stay in its lane.

This is the identity layer of the multi-tenant playbook: get it right and every other concern — permissions, secrets, spend — has a clean boundary to attach to. Here's the model Naïve uses and how to wire it up.

Why isn't one shared agent enough?

The shortcut is a single agent instructed to "only act for the current user." It demos well and fails in production. A prompt is a suggestion, and the ways it breaks are mundane: a prompt-injection payload in a document the agent reads, a mixed-up variable, a cache that outlives a request, a log line that captures another tenant's data.

Isolation that depends on the model behaving is not isolation. In a multi-tenant product, the blast radius of one slip is a real customer's account — so the boundary has to sit below the model, in infrastructure the prompt can't override.

What does "one agent per customer" actually look like?

Naïve models multi-tenancy in three layers:

Operator (your company)      ← you, the developer who signed up
  └── Account Kit            ← policy template: what a user's agent can do
        └── tenant user      ← one of your customers
              └── resources  ← cards, inboxes, vault, connections, sessions, ...
  • Operator is your workspace. It holds your API keys and your Account Kits.
  • Account Kit is a reusable policy template that says which primitives are enabled and which third-party apps a user may connect.
  • Tenant user is one of your customers. Crucially, it is not an auth subject — it never signs in. It's an opaque record you manage through the API, SDK, or CLI, and every resource the agent creates belongs to exactly one tenant user.

That last point is the whole game: cards, inboxes, vault entries, connections, and sessions each attach to a single tenant user, so isolation is a property of the data model rather than a runtime hope.

How does per-user isolation compare to a DIY build?

ConcernDIY multi-tenant agentNaïve model
Identity boundaryTenant IDs threaded through every call by handtenant_user every resource attaches to
Scoping a requestCustom "current tenant" plumbing + reviewnaive.forUser(customerId) scopes everything
Cross-tenant safetyDepends on you never making a mistakeStructural — a scoped client can't reach other users
Per-customer policyBespoke permission checks per userAssign an Account Kit; enforced server-side
Login vs isolationConflated (one account = one tenant)Separated — your app owns login; tenant user owns isolation

How do you wire it up?

Two calls cover the common case. Create a tenant user when a customer signs up, then scope every agent action to that user:

import { Naive } from "@usenaive-sdk/server";
const naive = new Naive({ apiKey: process.env.NAIVE_API_KEY! });
 
// on signup: one tenant user per customer
const user = await naive.users.create({ email: "customer@acme.com" });
 
// every call the agent makes is now isolated to that customer
const agent = naive.forUser(user.id);
await agent.email.send({ to: "lead@example.com", subject: "Hi", body: "..." });

The same surface works from the CLI (--user), MCP (a user_id argument), and REST (/v1/users/:user_id/...). And it scales down as cleanly as it scales up: a solo developer gets a default Account Kit and a default tenant user automatically, so calls with no explicit user just resolve to that default (per published docs).

Where does policy come in?

Isolation answers "whose resources?" — it doesn't answer "what is this agent allowed to do?" That's the job of the Account Kit you assign to each tenant user: it defines which primitives are enabled and which apps can be connected, enforced on every call. You author a few kits (say, Starter/Pro/Enterprise) rather than configuring ten thousand users one at a time.

That permission layer is covered in AI agent permissions, enforced at execution time, and the credentials each isolated agent holds are covered in secrets management for AI agents.

Where to start

Model one customer as a tenant user and route a single real action through forUser — send an email, read a connected inbox, make a small purchase. Confirm the resource comes back attached to that user and is invisible to another. Once one tenant is isolated end to end, every customer after that is the same two calls. The multi-tenancy docs are the source of truth for the model and the dual-mode resolution rules.

Frequently Asked Questions
What are multi-tenant AI agents?+
Multi-tenant AI agents are agents scoped per customer inside one product: every user of your app gets their own agent with its own identity, resources, and permissions, fully isolated from other users. Instead of one shared agent that's told to 'act for customer X,' each tenant has a distinct agent whose access is enforced by the platform, not by a prompt.
How do I give each user their own AI agent identity?+
Create a tenant user per customer and scope the agent's calls to it. On Naïve the model is Operator → Account Kit → tenant user → resources: you call `users.create()` on signup, then use `naive.forUser(customerId)` so every card, inbox, vault entry, and connection the agent touches belongs to that one tenant user and no other.
Is a tenant user the same as a login account?+
No. A tenant user is not an auth subject — it never signs in. It's an opaque record you manage through the API, SDK, or CLI to represent one of your customers. Your own app still handles end-user login; the tenant user is just the isolation boundary every Naïve resource attaches to.
How is one customer's agent isolated from another's?+
Every resource belongs to exactly one tenant user, and a scoped client (`naive.forUser(id)`) can only reach that user's resources. Isolation lives in the platform, so a bug in your prompt or app code can't cross tenants — the boundary is structural, not a natural-language instruction the model might ignore.
Can I use the same setup for a single agent and a multi-tenant app?+
Yes. The same API key works two ways: as a solo developer you get a default Account Kit and default tenant user with zero setup, and for a multi-tenant SaaS you call `users.create()` per signup and scope with `forUser`. When no user is specified, calls resolve to the key's default tenant user (per published docs).
What controls what each tenant's agent is allowed to do?+
An Account Kit — a reusable policy template you assign to a tenant user. It defines which primitives the agent can use and which third-party apps it may connect, enforced server-side. You author a few kits (Starter, Pro, Enterprise) instead of configuring every user individually. See the companion post on AI agent permissions for details.
NT
Naïve Team

Building the agent-native backend.

Keep reading