Multi-Tenant SaaS Architecture for Agent Workloads: Lessons From Building AI PRO CRM
AI PRO CRM is built on Laravel and PostgreSQL with shared-schema tenancy. These are conventional choices. What is less conventional is how agent workloads shaped the details.
Decision 1: shared schema with forced RLS
One schema, a workspace key on every tenant table, and PostgreSQL row-level security enabled and forced on all of them. Forcing matters: without it, a table owner bypasses the policies. With it, a query that forgets its filter cannot see other workspaces.
Decision 2: a runtime role that cannot bypass isolation
The application connects as a database role that is not a superuser, does not have BYPASSRLS and owns no tables. Those properties are checked on every deploy.
Decision 3: transaction-local tenant context
The current workspace is set inside each transaction, not on the connection, so a pooled connection cannot carry one workspace’s context into another request.
Decision 4: agents as identities, not shared keys
Agents are first-class identities with a human owner, a preset and their own hashed, expiring, revocable credentials. It is tempting to build the CRM first and hand agents a human’s API key later; that is how accountability ends up optional.
Decision 5: one narrow write, made safe to retry
Agents read through a remote MCP endpoint and have exactly one write: an internal note on an existing company. That write requires an idempotency key. A replay returns the original note; the same key with a changed payload is rejected.
Decision 6: limits at several levels
MCP calls are rate limited per credential and per workspace, and each agent has a daily cap on successful writes. These are safety fences, not billing.
Decision 7: append-only records
Workspace changes go to an append-only audit trail. Every MCP call is written to an agent call ledger with the agent, credential, tool, status, duration and record ids, but never the arguments, results or note bodies.
Decision 8: no hosted inference
AI PRO CRM does not run agents or call AI models; customers bring their own agents. Hosting models would add cost exposure and vendor dependence.
What we would tell another team
- Enforce isolation in the database, then in the app; never only in the app.
- Test that your runtime role really cannot bypass it, on every deploy.
- Decide what you will not build; for us that was hosted AI.
FAQ
Why Laravel?
Productivity and a mature ecosystem. The architecture would translate to other frameworks; the important guarantees live in PostgreSQL.
Why not schema-per-tenant?
Operational overhead grows with tenant count. Shared schema with forced row-level security keeps one migration path while the database still enforces isolation.