Building a Global SaaS: When AI Takes the MVP Path
A retrospective on building ContextFS with AI assistance and how AI tendency toward MVP solutions creates predictable category errors that require human architectural intervention.
When AI Takes the MVP Path
Why Abstraction Layers Matter in AI-Assisted Development
Key Metrics at a Glance
| 28+ | 7 | 2 weeks → 2 days | 24x |
|---|---|---|---|
| Releases shipped | Category errors identified | Abstraction tax reduced | Debugging improvement |
For Executives
AI coding assistants default to "make it work" solutions. Fast, but creates architectural debt. Most recurring issues traced to missing abstraction layers. The fix isn't less AI—it's knowing when to override AI's MVP instincts.
For Developers
AI writes working code that couples things that should be abstracted. SQLite everywhere? Works. Cached state? Works. Until it doesn't. This catalogs the category errors and abstractions AI skipped.
The Core Insight
When you ask an AI to implement something, it will give you working code.
The problem isn't that AI code doesn't work.
It's that AI systematically skips abstraction layers in favor of direct, coupled implementations.
What Is a Category Error?
A category error occurs when AI treats fundamentally different concerns as if they were the same thing.
The Pattern
┌────────────────────────────────────────────┐
│ "Store data" │
│ ↓ │
│ AI uses SQLite everywhere │
│ ↓ │
│ Category Error: Conflates local │
│ storage with cloud storage │
└────────────────────────────────────────────┘
Seven Category Errors We Encountered
| # | AI Sees | AI Does | Category Error |
|---|---|---|---|
| 1 | "Store data" | Uses SQLite | Local ≠ Cloud |
| 2 | "Add embeddings" | Installs PyTorch | Inference ≠ Training |
| 3 | "Cache this" | Process memory | Stateless ≠ Stateful |
| 4 | "Deploy" | Remote build | Dev ≠ Production |
| 5 | "Add analytics" | Direct API calls | First-party ≠ Third-party |
| 6 | "Check permissions" | Endpoint checks | Auth ≠ Authorization |
| 7 | "Store config" | Environment vars | Structured ≠ Strings |
Each produces working code that fails when the underlying assumption is violated.
Category Error #1
"Store Data" → SQLite Everywhere
The MVP Trap: AI sees "database" as a single concept.
What AI Did
# One database abstraction, used everywhere
class Storage:
def __init__(self):
self.db = sqlite3.connect("context.db")
Works in development. Fails when you need:
- Concurrent multi-user writes
- Vector embeddings (pgvector)
- Distributed transactions
The Missing Abstraction
┌─────────────────────────────────────────────┐
│ Storage Router │
├──────────────────┬──────────────────────────┤
│ LocalStorage │ CloudStorage │
│ (SQLite) │ (PostgreSQL) │
│ │ │
│ • Single user │ • Multi-tenant │
│ • Offline-first │ • Always-online │
│ • Lightweight │ • Scalable │
└──────────────────┴──────────────────────────┘
Impact
| Metric | Value |
|---|---|
| Time to fix after coupling | ~2 days |
| Time if abstracted upfront | ~2 hours |
| Abstraction tax | 10x |
Category Error #2
"Add Embeddings" → Install PyTorch
The MVP Trap: AI conflates "embedding model" with "full ML framework."
What AI Did
from sentence_transformers import SentenceTransformer
model = SentenceTransformer("all-MiniLM-L6-v2")
Result:
- 3GB of dependencies
- 270-second Docker builds
- GPU/CPU confusion
The Missing Abstraction
class EmbeddingBackend(Protocol):
def embed(self, texts: list[str]) -> list[list[float]]: ...
# Light option (default)
class FastEmbedBackend(EmbeddingBackend): ... # 500MB
# Heavy option (opt-in)
class TorchBackend(EmbeddingBackend): ... # 3GB
Impact
| Metric | Before | After | Improvement |
|---|---|---|---|
| Install size | 3GB | 500MB | 6x |
| Docker build | 270s | 51s | 5x |
| Complexity | High | Low | Much simpler |
Category Error #3
"Cache for Performance" → Process Memory
The MVP Trap: AI treats caching as a local optimization.
What AI Did
class MCPServer:
def __init__(self):
# Cached at startup!
self.collection = chroma.get_collection("memories")
Works until the database is rebuilt. Then: "Collection does not exist" errors that look like corruption.
The Missing Abstraction
# Connection factory, not cached connection
class CollectionFactory:
def get_collection(self) -> Collection:
return self.client.get_collection("memories")
Impact
| Before | After |
|---|---|
| Hours debugging per incident | 5 minutes |
| Improvement: 24x |
Category Error #4
"Deploy the App" → Remote Build
The MVP Trap: AI sees deployment as "run the build somewhere else."
What AI Did
railway up # Push source, build remotely
Result:
- 30% failure rate (timeouts)
- Inconsistent builds
- Slow debugging
The Missing Abstraction
Source Code → Docker Image → Production
(Git) (Pre-built) (Instant)
│ │ │
BUILD ARTIFACT DEPLOY
(CI/CD) (Registry) (Railway)
Impact
| Approach | Reliability |
|---|---|
| Remote build | 70% |
| Pre-built image | 98% |
Category Error #5
"Add Analytics" → Direct Third-Party Calls
The MVP Trap: AI treats third-party services as first-party endpoints.
What AI Did
posthog.init('key', {
api_host: 'https://us.i.posthog.com'
})
Problem: VPNs and ad blockers block us.i.posthog.com.
The Missing Abstraction
Your Domain
┌────────────────────────────────────┐
│ /ingest/* → us.i.posthog.com │
└────────────────────────────────────┘
↑
Users see YOUR domain
Blockers don't block YOU
The Fix
// 5 lines AI wouldn't think to add
rewrites: [{
source: '/ingest/:path*',
destination: 'https://us.i.posthog.com/:path*'
}]
Category Error #6
"Check Permissions" → Endpoint-Level Checks
The MVP Trap: AI adds checks where you ask, not where they belong.
What AI Did
async def get_memory(id: str, user: User):
memory = await db.get_memory(id)
if memory.user_id != user.id: # Manual check
raise HTTPException(403)
One missed endpoint = data leak.
The Missing Abstraction
class TenantScopedRepository:
def __init__(self, user_id: str):
self.user_id = user_id
async def get_memory(self, id: str):
# Isolation IMPOSSIBLE to forget
return await db.query(Memory).filter(
Memory.id == id,
Memory.user_id == self.user_id
).first()
Impact
| Approach | Security | Burden |
|---|---|---|
| Endpoint checks | Fragile | Must remember |
| Query-layer scope | Robust | Zero |
Category Error #7
"Store Config" → Environment Variables
The MVP Trap: AI treats all configuration as string key-value pairs.
What AI Did
TYPE_SCHEMAS = json.loads(os.environ.get("TYPE_SCHEMAS", "{}"))
No validation. No types. No IDE support.
The Missing Abstraction
class DecisionData(BaseModel):
decision: str # Required
rationale: str # Required
alternatives: list[str] = []
# Type-safe usage
def process(data: DecisionData):
print(data.decision) # IDE knows this
Impact
Result: 22 typed schemas, 71 tests, zero runtime type errors.
The Meta-Pattern
Every category error follows the same structure:
┌─────────────────────────────────────────┐
│ Request: "Do X" │
│ ↓ │
│ AI: Direct implementation │
│ ↓ │
│ Works: Yes (MVP achieved) │
│ Missing: Abstraction layer │
│ ↓ │
│ Result: Tech debt when concerns │
│ diverge │
└─────────────────────────────────────────┘
AI optimizes for "does it work?" not "will it scale?"
Decision Framework
When to Override AI's MVP Instincts
| AI Suggests | Ask Yourself | Override If |
|---|---|---|
| Single database | Different requirements for local/cloud? | Yes → Separate layers |
| Heavy dependency | Need full capability? | No → Lighter alternative |
| In-memory cache | Long-running process? | Yes → Connection factory |
| Direct deploy | Need reproducibility? | Yes → Pre-build artifacts |
| Direct third-party | Users might have blockers? | Yes → Reverse proxy |
| Endpoint checks | Cross-cutting concern? | Yes → Query-layer |
| String config | Has structure? | Yes → Type it |
Quantified Impact
Abstraction Tax: Before vs After
| Category | MVP Cost | Fix Time | If Upfront |
|---|---|---|---|
| Database split | 2 days | 2 days | 2 hours |
| Embedding backend | Ongoing | 4 hours | 1 hour |
| Cache invalidation | 2 hrs/incident | 15 min | 30 min |
| Deployment | 30% failures | 1 day | 2 hours |
| Third-party proxy | 4 hours debug | 30 min | 15 min |
| Tenant isolation | Audit time | 1 day | 4 hours |
| Typed config | Weeks | Weeks | 1 day |
Summary
| Total abstraction tax paid | If architected upfront |
|---|---|
| ~2 weeks | ~2 days |
The Eight Abstractions AI Will Skip
- Storage topology — Local vs. cloud are different categories
- Inference backend — Separate model from framework
- Cache lifecycle — Explicit invalidation, not implicit
- Build artifacts — Separate build from deploy
- First-party boundary — Proxy third-party through your domain
- Tenant scope — Enforce at query layer, not endpoint
- Type system — Structure your structured data
- Configuration layer — Separate what changes from what doesn't
Conclusion
AI as Architect's Assistant, Not Architect
AI coding assistants are extraordinarily good at implementing what you ask for.
They are systematically bad at asking "should this be abstracted?"
The solution isn't less AI—it's using AI within an architectural frame you provide.
The Pattern
AI builds MVPs. Humans build architectures. The best results come from knowing when AI's MVP instinct needs a proper abstraction layer.
The Recursive Insight
ContextFS was built almost entirely with AI assistance. The issues here weren't AI failures—they were human failures to provide architectural constraints.
We now use ContextFS itself to remember these patterns. Future AI sessions start with abstractions already defined.
Every problem solved becomes searchable knowledge for future problems.
ContextFS is persistent memory infrastructure for AI coding agents—built with AI, for AI.