You have a SaaS idea that uses AI agents. Maybe it's a RevOps platform that autonomously manages your sales pipeline, or an inventory system that forecasts demand, or a procurement tool that matches tenders to company profiles. How do you actually architect it?
After building three AI-powered SaaS products — RevAgent (autonomous RevOps), BandiFinder (procurement matching), and FiGo (AI fitness marketplace) — here's the architecture that works, the decisions that matter, and the traps that waste months.
The Stack Decision
Before writing code, you need to decide on four layers:
| Layer | Options | My Default |
|---|---|---|
| Frontend | Next.js, Remix, SvelteKit | Next.js (App Router) — best ecosystem for AI features |
| API | Next.js API routes, Hono, Express, FastAPI | Hono for agent APIs, Next.js routes for frontend APIs |
| Database | Supabase (Postgres), PlanetScale, Neon | Supabase — auth + DB + realtime + storage in one |
| AI Orchestration | LangGraph, Vercel AI SDK, custom | LangGraph for complex agents, Vercel AI SDK for chat UIs |
Why these defaults:
- Next.js: Server Components reduce client bundle, App Router handles layouts/loading/error states, and the Vercel AI SDK integrates natively for streaming chat UIs.
- Hono: Lightweight, fast, runs everywhere (Vercel, Cloud Run, Bun). Better than Express for API-heavy agent backends that don't need React.
- Supabase: Postgres + Auth + Row Level Security (RLS) in one. RLS is the cheapest way to implement multi-tenancy — security enforced at the database level, not the application level.
- LangGraph: When your agent needs cycles (retry, re-evaluate, branch), durable execution (survive crashes), or human-in-the-loop, LangGraph is the right choice. For simple request→LLM→response patterns, the Vercel AI SDK is simpler.
Architecture Overview
┌─────────────────────────────────────────────────────────┐
│ Next.js Frontend │
│ (Dashboard, Settings, Chat UI, Billing Portal) │
└──────────────┬────────────────────────┬──────────────────┘
│ │
Vercel AI SDK REST/tRPC
(streaming chat) (CRUD, config)
│ │
┌──────────────▼────────────────────────▼──────────────────┐
│ API Layer (Hono) │
│ /v1/agents, /v1/deals, /v1/settings, /v1/billing │
└──────┬──────────┬──────────┬──────────┬──────────────────┘
│ │ │ │
┌──────▼───┐ ┌───▼────┐ ┌───▼────┐ ┌───▼────────┐
│ LangGraph│ │Supabase│ │ Stripe │ │ CRM/Email │
│ Agents │ │Postgres│ │Billing │ │ Integrations│
│ │ │+ Auth │ │ │ │ (MCP) │
└──────────┘ └────────┘ └────────┘ └────────────┘
Multi-Tenancy: The Decision That Shapes Everything
Multi-tenancy is the most consequential early architectural decision. You have three options:
Option 1: Shared Database + Row Level Security (Recommended for 0→PMF)
Every tenant's data lives in the same Postgres database. Supabase RLS policies enforce isolation:
-- Every table has a tenant_id column
ALTER TABLE deals ENABLE ROW LEVEL SECURITY;
CREATE POLICY "Tenants see only their data"
ON deals FOR ALL
USING (tenant_id = auth.jwt() ->> 'tenant_id');Pros: Simple, cheap, one database to manage. Supabase handles auth + RLS together. Cons: Noisy neighbor risk at scale (one tenant's heavy query affects others).
This is what RevAgent and BandiFinder use. It works until you have ~500 tenants or a tenant processing millions of records.
Option 2: Schema-Per-Tenant
Each tenant gets their own Postgres schema within the same database:
CREATE SCHEMA tenant_acme;
CREATE TABLE tenant_acme.deals (...);Pros: Better isolation, per-tenant backups possible, no RLS overhead. Cons: Migration complexity (run ALTER TABLE across N schemas), connection pooling challenges.
Option 3: Database-Per-Tenant
Each tenant gets their own database. Maximum isolation, maximum operational overhead. Only for enterprise with strict compliance requirements (healthcare, finance).
My advice: Start with Option 1. You can always migrate to Option 2 when you hit scaling limits. Don't over-engineer isolation before you have paying customers.
Agent Architecture
The Agent Layer
Your AI agents are the core value proposition. Architect them as independent, composable services — not monoliths.
# Each agent is a separate LangGraph graph
from langgraph.graph import StateGraph, START, END
# Risk Agent — scores deal health
risk_graph = StateGraph(RiskState)
risk_graph.add_node("fetch_data", fetch_deal_data)
risk_graph.add_node("analyze", analyze_signals)
risk_graph.add_node("score", compute_risk_score)
# ... edges, compile
# Forecast Agent — predicts revenue
forecast_graph = StateGraph(ForecastState)
forecast_graph.add_node("gather_pipeline", gather_pipeline_data)
forecast_graph.add_node("calculate", run_forecast_model)
# ... edges, compile
# Orchestrator — chains agents based on events
async def on_deal_updated(deal_id: str):
risk = await risk_agent.invoke({"deal_id": deal_id})
if risk["risk_score"] > 0.7:
await escalation_agent.invoke({"deal_id": deal_id, "risk": risk})
await hygiene_agent.invoke({"deal_id": deal_id})Why separate agents instead of one big agent?
- Independent deployment: Update the risk scoring logic without touching forecasting.
- Independent scaling: The chat agent handles 100x more requests than the weekly brief generator.
- Independent testing: Each agent has its own evaluation dataset and quality metrics.
- Independent cost tracking: Know exactly which agent is burning tokens.
Connecting Agents to External Systems (MCP)
Use MCP (Model Context Protocol) servers for CRM, email, and other integrations:
from langchain_mcp_adapters.client import MultiServerMCPClient
client = MultiServerMCPClient({
"hubspot": {"transport": "http", "url": "https://hubspot-mcp.yourapp.com/mcp"},
"gmail": {"transport": "http", "url": "https://gmail-mcp.yourapp.com/mcp"},
})
tools = await client.get_tools()
agent = create_agent("gpt-4o-mini", tools)Build MCP servers once, share across all your agents. When you add Salesforce support, you add one MCP server — every agent gets it automatically.
The Hybrid Pattern: LLM + Rules
Don't use LLMs for everything. Rule-based logic is faster, cheaper, and deterministic:
async def score_risk(deal: Deal) -> float:
# Rule-based signals (instant, free)
score = 0.0
if deal.days_open > 90: score += 0.3
if deal.last_activity_days > 14: score += 0.2
if deal.stage == "negotiation" and deal.days_in_stage > 30: score += 0.2
# LLM-based signals (only when rules aren't enough)
if deal.has_email_threads:
sentiment = await llm_analyze_sentiment(deal.email_threads)
score += (1 - sentiment) * 0.3
return min(score, 1.0)RevAgent uses LLMs for email sentiment analysis and natural language queries. Everything else — risk scoring heuristics, forecast calculations, CRM field validation — is deterministic code. This keeps costs 80% lower than an all-LLM approach.
Billing: Stripe + Usage-Based Pricing
AI SaaS has a unique billing challenge: your costs scale with usage (LLM tokens, API calls) but customers expect predictable pricing.
The 5-Tier Model
Pilot → Free → 5 deals, basic risk detection
Starter → $49/mo → 50 deals, full pipeline
Growth → $149/mo → Forecast agent, weekly briefs
Enterprise → $399/mo → Chat agent, SAML SSO
Enterprise+ → $799/mo → Dedicated support, custom integrations
Implementation:
// Stripe Checkout + webhooks
const session = await stripe.checkout.sessions.create({
mode: 'subscription',
line_items: [{ price: plan.stripePriceId, quantity: 1 }],
subscription_data: { trial_period_days: 7 },
success_url: `${BASE_URL}/settings/billing?success=true`,
cancel_url: `${BASE_URL}/settings/billing`,
});
// Webhook: provision tenant on successful payment
app.post('/v1/billing/webhook', async (c) => {
const event = stripe.webhooks.constructEvent(body, sig, secret);
if (event.type === 'checkout.session.completed') {
await provisionTenant(event.data.object);
}
});Feature Gating
Gate agent features by plan, not by seat count:
async function canUseAgent(tenantId: string, agentName: string): Promise<boolean> {
const plan = await getTenantPlan(tenantId);
const agentAccess: Record<string, string[]> = {
'pilot': ['risk'],
'starter': ['risk', 'hygiene', 'follow_up'],
'growth': ['risk', 'hygiene', 'follow_up', 'forecast', 'brief'],
'enterprise': ['risk', 'hygiene', 'follow_up', 'forecast', 'brief', 'chat'],
};
return agentAccess[plan]?.includes(agentName) ?? false;
}Authentication & Authorization
Supabase Auth + RBAC
Supabase Auth handles signups, logins, and OAuth. Add RBAC with a simple roles table:
CREATE TABLE user_roles (
user_id UUID REFERENCES auth.users(id),
tenant_id UUID REFERENCES tenants(id),
role TEXT CHECK (role IN ('admin', 'member', 'viewer')),
PRIMARY KEY (user_id, tenant_id)
);
-- RLS policy: only admins can modify settings
CREATE POLICY "Admins only" ON tenant_settings
FOR ALL USING (
EXISTS (
SELECT 1 FROM user_roles
WHERE user_id = auth.uid()
AND tenant_id = tenant_settings.tenant_id
AND role = 'admin'
)
);For Enterprise: SAML SSO
Enterprise customers expect SSO. Supabase supports SAML natively. RevAgent gates SSO behind the Enterprise plan — it's a zero-cost upsell that enterprise buyers require.
Deployment
The Simple Path: Vercel + Supabase
Frontend (Next.js) → Vercel
API (Hono) → Vercel Functions (Fluid Compute)
Agents (LangGraph) → LangSmith Deployments or Vercel Functions
Database → Supabase
Billing → Stripe
Why Vercel: Zero-config deployments, preview URLs for every PR, automatic scaling. Fluid Compute reuses function instances across concurrent requests — significantly better cold start behavior than traditional serverless.
Scheduled Jobs
AI agents often need scheduled execution (daily risk scans, weekly briefs, data syncing):
Daily risk scan → 9am UTC → POST /v1/jobs/daily-risk-scan
Weekly brief → Mon 8am → POST /v1/jobs/deliver-weekly-brief
CRM sync → 6am UTC → POST /v1/jobs/sync-deals-from-crm
Use Vercel Cron Jobs or Supabase pg_cron — both work. For RevAgent, cron jobs trigger the API which invokes the relevant LangGraph agent.
The Build-vs-Buy Matrix
Not everything should be custom-built:
| Component | Build | Buy | Why |
|---|---|---|---|
| Agent orchestration | Build (LangGraph) | — | Core IP, needs full control |
| Auth | — | Buy (Supabase Auth) | Commodity, security-critical |
| Billing | — | Buy (Stripe) | Commodity, compliance-heavy |
| CRM integration | Build (MCP server) | — | Custom to your data model |
| Email/Slack notifications | — | Buy (Resend, Slack API) | Commodity |
| Monitoring | — | Buy (LangSmith) | Specialized for LLM observability |
| Vector store | Depends | Supabase pgvector | Build if scale demands Pinecone |
Rule: Build what differentiates your product. Buy everything else.
Common Mistakes
1. Building the agent before the product. Start with a manual version — even if it's you doing the work. Validate that customers want the outcome before automating with AI.
2. One mega-agent. Split into focused agents from day one. A monolithic agent is impossible to debug, test, and scale independently.
3. No fallback when the LLM is down. Every agent path needs a deterministic fallback. LLM APIs have outages. Your customers don't care why.
4. Charging per seat instead of per value. AI SaaS costs scale with usage, not users. Price by the value delivered (deals managed, forecasts generated) not by headcount.
5. Skipping evaluation datasets. You can't improve what you can't measure. Build 50 test cases before your first customer.
6. Over-engineering multi-tenancy. Start with shared DB + RLS. You can always migrate later. Don't build database-per-tenant before you have 10 customers.
Related Posts
- Building AI Agents with LangGraph: From Prototype to Production — the orchestration framework powering the agent layer
- Prompt Engineering for Production: Beyond ChatGPT Tricks — the prompt patterns that make agents reliable at scale
- MCP (Model Context Protocol): Connecting AI Agents to Real Tools — how to connect agents to CRMs, databases, and external APIs
Building an AI-powered SaaS? I've architected and shipped three from scratch. Get in touch or book a call.