lmn.bar — Senior PM AI case study
AI-native operations console for DTC brands. Streams Claude-generated recommendations grounded in live Shopify, Meta Ads, Google Ads, and Klaviyo data via Composio MCPs, with guardrails, audit, and cost controls.
What it is, in one paragraph
lmn.bar is a multi-tenant SaaS console that gives DTC operators one place to see revenue, contribution margin, ROAS by channel, and inventory health, plus a streaming AI agent that can read across their connected tools and propose (or, where allowed, take) actions. Each tenant maps to one Shopify store and connects Meta Ads, Google Ads, and Klaviyo through Composio MCPs. Claude (Sonnet 4.5 default, Opus 4.1 / Haiku 4.5 fallback) does the reasoning over tool-bound schemas, with server-side guardrails on every parsed recommendation.
Problem it solves
•Revenue, ad spend, and inventory live in different tools, so operators rebuild the same pivots in spreadsheets every week.
•Channel ROAS and contribution margin require stitching Shopify orders + COGS + Meta + Google by hand.
•Creative performance decisions (scale, kill, evergreen, refresh) are gut-feel because nobody has a comparable per-ad score.
•Stockouts and broken-run inventory hit revenue before anyone notices.
•Generic AI chat tools can summarize a CSV but can't safely act on Shopify or change a Meta budget; agents that can act don't have guardrails or audit.
What lmn.bar solves, concretely
○One financial truth across Shopify + ads. Revenue, COGS, ad spend, contribution margin, and unit economics in one view, over time and in totals.
○ROAS by channel and per-creative health score. Meta and Google ads with ROAS vs target, evergreen status, and a 1-week vs 2–4-week trend so spend slippage is visible early.
○Inventory awareness tied to revenue. Stock levels, low-stock, coverage, and broken-run alerts at variant level.
○Grounded AI recommendations, not chat fog. Streaming Claude output with mandatory tool calls against the user's own data, parsed into structured recommendations and validated against tenant guardrails.
○Safe action surface. Guardrails on max_scale_percent, min_roas, max_daily_spend_increase, plus custom rules; every action is audit-logged; agent endpoint defaults to read-only MCP tools.
○Resilience and cost control. Memory + DB metrics cache, HMAC-verified Shopify webhooks that invalidate on order events, per-tenant Claude cost tracking with budget alerts and optional hard block, fallback to cached or metric-derived recommendations on Claude failure.
Tech stack
Frontend
•Next.js 14+ (App Router, Server Components, TypeScript strict)
•React 18, Tailwind CSS, shadcn/ui + Radix UI primitives
•TanStack Query for client data fetching and dedupe
•Recharts for charts, Phosphor Icons, Sonner toasts
•react-markdown + remark-gfm for streaming agent output
Backend
•Next.js API Routes (Node runtime), Vercel deploy
•Supabase Postgres with Row Level Security for multi-tenant isolation
•Supabase Auth with multi-tenant profiles + memberships
•36+ tracked migrations in supabase/migrations/
•Stripe billing with webhook reconciliation
•Resend + React Email for transactional, Loops for lifecycle
•Attio CRM sync, Pipedream, Notion, GHL webhook integrations
AI layer (Claude + Composio MCPs)
•Anthropic SDK with snapshot-versioned model IDs and a MODEL_FALLBACK_ORDER
•Default claude-sonnet-4-5-20250929; Haiku 4.5 for fast tasks; Opus 4.1 for deep reasoning
•Streaming agent endpoint POST /api/agent returns a ReadableStream; supports per-tenant rate limits and structured recommendation JSON appended at end of stream
•Multi-round tool calling: tool-use detection in stream, execution via Composio MCP, conversation continuation
•Composio SDK as the MCP layer for Shopify, Meta Ads, Google Ads, Klaviyo (Stripe deferred)
•Tool definitions in lib/claude/tools/ mapping MCP actions to Claude input_schema; getAllTools({ readOnlyForAgent: true }) strips mutating tools by default
•Dynamic tool filtering by which MCPs the tenant has actually connected
•Prompt builder with prompt cache (5-min TTL), guardrails injector, metrics injector, brand-context injector, and tool injector
•Response pipeline: response-parser → recommendation-formatter → insight-extractor → response-processor (parses streamed text into validated recommendations against contracts)
•Server-side recommendation guardrails re-validate parsed output, same as /api/actions
Reliability and ops
•Rate limiter + p-queue request queue (4 req/min default, configurable)
•Retry with exponential backoff on 429/5xx via withClaudeRetry
•Error classifier + user-friendly messages + monitoring (logClaudeError, getErrorStatistics)
•Fallback recommendations from cache or computed from cached metrics when Claude is unavailable
•Cost tracker per tenant + model, budget alerts with optional hard block
•Cache stack: in-memory 30s + Postgres 5min for metrics; ShopifyQL fresh-only for MVP correctness; HMAC-verified Shopify webhooks (orders/create, paid, updated, cancelled, refunds/create) trigger tenant cache invalidation
•Single full-fetch + client-side filter/sort/pagination for inventory and ads tables (instant tab/sort/page changes)
Quality, analytics, and CI
•Vitest for unit, integration, and component tests; mocks via lib/contracts/mocks.ts
•GitHub Actions CI: type, lint, format, test, build; pre-push git hook runs the same
•PostHog as primary analytics + error tracking (client + server, with instrumentation.ts flushing on edge), Data Pipeline destinations to Meta CAPI and Google Enhanced Conversions
•Meta Pixel with event_id deduplication shared with PostHog/CAPI
•hCaptcha on marketing forms
Architecture principles (from .cursorrules)
•Interface-Driven Development via lib/contracts/ (database, api, services, mocks) so multiple work streams progress in parallel against a typed boundary
•Mock-first development; switch to real APIs when ready
•Multi-tenant isolation enforced server-side via RLS and a tenant context utility
•Security first: input validation (Zod), guardrails enforced server-side, audit log on all actions
AI / agent architecture (deep dive for interview)
Why this shape, not a generic chat wrapper:
○Grounded by tools, not by prompt. The system prompt is built per-request with live tenant metrics, guardrails, brand context, and a filtered tool list reflecting that tenant's connected MCPs. The model can't hallucinate ads it can't see; it has to call a tool.
○Server-validated structured output. Streaming text is parsed into structured Recommendation objects (type, confidence, revenue impact, action params), then re-checked against guardrails before they're allowed to surface as actionable. Same validator runs in /api/actions so prompt-injected 'just do it' attempts fail at execution.
○Read-only by default. /api/agent defaults to readOnlyMcpTools: true. Mutating actions live behind the actions API with explicit user approval and audit logging.
○Streaming UX first-class. Custom streamClaude helper wraps MessageStream into a ReadableStream and runs processAgentResponseAsync so the recommendation JSON is appended at end-of-stream without blocking the visible text.
○Cost and reliability are PM concerns, not just infra.
•Snapshot-versioned model IDs (no aliases in prod)
•Three-tier model strategy by task complexity
•Budget per tenant with optional hard block on /api/agent
•Three-step fallback: cached recs → metric-derived recs → empty with explanation
○Chat with persistence. Conversations and messages persisted in chat_conversations / chat_messages; POST /api/agent returns X-Conversation-Id. Optional 'share AI conversations' analytics flag.
Data model highlights
•tenants, tenant_settings, tenant_stores, users, profiles, memberships (multi-tenant + multi-store ready)
•mcp_auth_configs (per-tenant Composio Auth Config IDs, with env-var fallback)
•shopify_connections, meta_ads_connections, google_ads_connections
•metrics_cache, shopifyql_cache with date-range and tenant-scoped uniqueness
•recommendations, guardrails, inventory_exclusions
•chat_conversations, chat_messages (with archive support)
•outbox_events for reliable webhook fan-out
•Stripe trial + billing fields on tenants
All tables RLS-scoped to tenant; service-role used only inside vetted server paths (e.g. webhook cache invalidation).
Product surface
•Marketing site: hero with hosted product video, features carousel (financial, marketing, inventory, product), pricing, contact, scorecard ('are you a fit?') and operator health-check funnels
•Auth: signup, login, forgot/reset password; Shopify install/bootstrap with conflict resolution; OAuth state + base URL utilities
•Onboarding: status + completion flow gated on Shopify connect, COGS, billing
•Dashboard
•Financial overview (KPIs: AOV, CAC, breakeven ROAS, cost-cap ROAS, new vs returning)
•Ad performance with creative health cards and per-channel ROAS
•Inventory section with broken-run + low-stock awareness
•ShopifyQL section with contribution margin chart, total sales chart, financial breakdown
•Streaming chat-with-data widget (floating + full panel) with conversation history, archive, rename
•Recommendations panel with details modal and action confirmation
•Query status widget and last-updated indicator
•Settings: connection cards (Shopify, Meta, Google), financial settings, inventory exclusions, privacy, MCP connections health
•Admin / dev: health-check page, components showcase, demo and test-agent routes
What I'd talk about in an interview
1. Choosing 'tools over RAG' for a vertical agent.
Because the data lives in live SaaS tools that already have great APIs and constant change, MCPs + Composio give us auth, schema, and execution for free. RAG would freeze a snapshot and need a sync pipeline.
2. Where I drew the safety line.
Default read-only on the agent endpoint, structured output validation, and a single guardrails validator shared between agent recommendations and the actions API. Audit log + budget caps make the surface defensible to a brand.
3. Streaming as a product feature, not just a tech detail.
First-token latency is the perceived 'is this thing alive' signal. Wrapping MessageStream in a ReadableStream and parsing structured recs at end-of-stream lets us show text immediately and surface actions only after server-side guardrail checks.
4. Multi-tenant from day one.
RLS + tenant-scoped Composio Auth Config IDs, with env-var fallback for shared OAuth apps during early adoption. This is the boring choice that makes 'add a second customer' a 1-day task.
5. Cache policy that admits what it doesn't know.
Metrics cache is generous (memory 30s, DB 5min), but ShopifyQL is fresh-only for MVP correctness. Webhooks invalidate on order/refund events. Picking the right consistency for the right surface beats 'cache everything.'
6. Cost is a product surface.
Per-tenant cost tracking, budget alerts, and an optional hard block on /api/agent keep AI economics inside a real margin model, not a 'see you next month' surprise.
7. Interface-Driven Development to ship faster solo.
lib/contracts/ defines the API/service/database boundary. Mocks ship first, real implementations swap in behind them. The same approach scales to a team without rewrites.
Quick stats
•Stack tags: Next.js 14, React 18, TypeScript, Supabase, Postgres, RLS, Vercel, Anthropic Claude (Sonnet 4.5 / Opus 4.1 / Haiku 4.5), Composio MCP, Tailwind, shadcn/ui, TanStack Query, Recharts, Stripe, Resend, Loops, Attio, PostHog, Meta Pixel, Vitest
•Integrations: Shopify, Meta Ads, Google Ads, Klaviyo (Stripe MCP deferred)
•Tests: Vitest unit + integration + component; CI on push/PR via GitHub Actions; pre-push hook runs full pipeline locally
•Migrations tracked: 36+ in supabase/migrations/
•Repo: lmnbar-v2 (private)