Playbook / Principal search path / Multi-tenant isolation — the 8-minute whiteboard

Multi-tenant isolation — the 8-minute whiteboard

I rehearse this aloud before every HM call. Same design whether tenant means org unit, SaaS customer, CRM account, or startup workspace.


What I actually say first (~60s)

I’d start with the threat model, not a tenancy column. For agents the leak is usually RAG bleed — Tenant A’s query pulling Tenant B’s chunks — or a tool call as the wrong tenant. So authorization happens before ranking, and every write carries tenant_id through a gateway. If we can’t fail a negative test in CI, we don’t have isolation. We have a slide.

Then I go to the board.

What I refuse: a shared default corpus in prod. Fail-open gateway on irreversible tools. Cache keyed only on prompt text. An “admin bypass” with no dual control.


Whiteboard (8–10 min)

1. Threat model

  • Cross-tenant RAG chunk leak on a shared index
  • Shared agent memory / conversation bleed
  • Confused deputy: agent A mutates tenant B’s CRM or publish target
  • Prompt injection that tries to escalate tenant scope
  • Semantic cache keyed only on prompt text → cache bleed
  • Admin / debug paths that skip AuthZ

2. Isolation layers

Request
  → Identity: JWT with tenant_id + principal + roles/clearance
  → AuthZ before retrieval: filter corpus by tenant + clearance BEFORE hybrid rank
  → Data plane: per-tenant collections / namespaces / RLS; no shared default corpus in prod
  → Control plane: per-tenant policy bundles, tool allowlists, HITL rules
  → Runtime: tenant-keyed cache + budgets; no cross-tenant keys
  → Audit: signed logs with tenant_id on every tool decision

3. Side effects

Any write — CRM, publish, notify, deploy, payment:

  1. Tool args must include tenant_id matching the JWT claim.
  2. Gateway rejects mismatch or missing tenant.
  3. High-risk actions → HITL with tenant context visible to the approver.
  4. Fail-closed in production strict mode if the gateway is unreachable.

4. Negative evals (I say these out loud)

IDAssertion
NT-1Tenant A query never returns Tenant B chunks
NT-2Tenant A agent cannot mutate Tenant B resources
NT-3Cache hit for A must never serve B’s embedding or response
NT-4Prompt injection cannot widen tenant scope
NT-5Gateway deny + audit event when tenant mismatches on a tool call

Wire NT-1..NT-5 as CI fixtures. Same discipline as golden-eval-registry.

5. Week 1–4 on their stack

WeekShip
1JWT claims (tenant_id, principal); reject unauthenticated retrieve/tool
2Retrieval filter / collection isolation; negative RAG tests
3Gateway enforces tenant on tool args; HITL on writes
4Tenant-keyed cache/budgets; signed audit; NT suite in CI

If they ask “how would you ship this here?”

Week one is identity — no anonymous retrieve, no tool without tenant. Week two is the filter-before-rank and a test that must fail if Tenant B leaks. Week three is the gateway on writes. Week four is cache keys and the NT suite in CI. I wouldn’t start with a multi-region rewrite.

6. Honesty sentence (required)

Live free-tier demos may use a Demo Principal and an in-memory registry so a panel can actually click them. The production design is Strict/JWT principal, durable policy store, AuthZ-before-ranking, and fail-closed side effects. I can walk the Strict path and the ADRs. Demos are labeled Demo vs Strict so nobody’s surprised.


Failures I name if they push

FailureWhat I’d do
Shared vector collectionPer-tenant collection or mandatory metadata filter pre-rank
Tool without tenantSchema require + gateway check
Approver sees the wrong tenantHITL UI shows tenant + resource IDs
Embed cache bleedCache key = hash(tenant_id + model + content)
“Admin bypass”Break-glass role with dual control + audit

Rendering architecture diagram…


If they ask “have you built this?”

Access-before-ranking under a Strict/JWT principal — that’s the RAG path a panel can inspect. Gateway, HITL, audit is the governance plane. Lucid is production outcomes, not those binaries. Honest gap: free-tier demos are labeled Demo vs Strict. I won’t sell an in-memory registry as multi-tenant SaaS.


Follow-ups I’d answer without flipping to a new pitch

“Can’t we just filter after retrieval?”

That’s the leak. Ranking on a shared index and filtering later still embeds Tenant B into the candidate set — and into caches, traces, and sometimes the prompt. Filter first. Rank second.

“What if the gateway is down?”

For irreversible tools I fail closed. Reads can degrade. Writes don’t get a free pass because Redis was sad.

“Isn’t this overkill for an internal tool?”

Internal is still multi-tenant if two orgs, two clearances, or two CRM accounts share a graph. I’d thin the control plane. I wouldn’t skip tenant on the tool args.

“Shared vs dedicated inference?”

Shared until a tenant pays for isolation or burns everyone else’s TPM. Identity spine stays the same. That’s the deep path.

“Can’t we put tenant in the system prompt?”

That’s a hint, not a control. Completions will ignore it under injection. Tenant comes from the token, into the filter and the tool args, before the model speaks.

“We already share one index.”

Mandatory pre-rank filter plus a negative test this week. Per-tenant collections when the leak class or the contract says so. I wouldn’t halt the product for a re-platform.


Close the board

I’d ship isolation as negative tests in CI, not as a slide. If tenants can leak in eval, the platform doesn’t ship.

What I’d ask them before I leave the board: where retrieval sits relative to authZ today, and what the riskiest write an agent can already do.

Go deeper before a platform HM: Multi-tenant deep path.