Learning to build with AI, by building with AI
Building notes from LearnWise : what changed when I swapped a no-code builder for a coding agent
The hardest part of building an AI product isn't the AI.
It took nine failed infrastructure pull requests to prove it. I almost built LearnWise my AI learning platform on a no-code app builder instead of a coding agent. Writing down why I switched taught me more than the switch itself.
LearnWise runs on one idea: you don't learn AI by watching a tutorial. You learn it by shipping something.
Every module ends in a real project, personalized to your role and your tools. Learn, build, share that's the whole loop. This post isn't really about LearnWise, though. It's about one early decision how to build it that rewired how I think about software
💡 Why I started with Lovable. The pitch was compelling. Describe the app in plain English, get a live product back so I picked it for the obvious reason, skip infrastructure, get something live in minutes.
To be fair, it's a genuinely good tool. A prototype, a landing page, a simple CRUD app, Lovable is good at exactly that, and this isn't a knock on it.
It stopped fitting the moment the project stopped being simple:
→ The backend needed real control, Python and FastAPI, not a managed black box, because the AI cost-governance logic had to own the full request flow.
→ Type safety had to span two languages off one shared schema. Not a shape Lovable reasons about.
→ Infrastructure had to be inspectable, real IAM roles (who can touch what), real CI, not a chat window.
→ Me and my friend needed to touch this code, with real review not one hosted conversation thread.
🎯 The moment I knew
It didn't come from writing code. It came from writing the spec. At some point the document stopped reading like a spec. It started reading like a standing contract addressed to an agent.
Lines like:
"Never invent a tool or model name that isn’t specified ask before changing it."
"Never skip the cost-caching step it’s the difference between this being viable and burning through runway."
"Stop here and wait for review before continuing."
You don't type that into a chat box once and move on. That's principles meant to hold context across months and dozens of pull requests. So I moved to a terminal-based coding agent real shell, real git, real gcloud CLI, operating directly on the actual repo, and this has been so liberating and helped be learn quicker.
🔑 The architecture, and why each piece is there
One repo, split across two deploy targets:
→ React (Vite + Tailwind), deployed to Firebase Hosting.
→ Python/FastAPI backend on Cloud Run stateless, scales to zero when idle.
→ Same-origin routing between the two. No CORS (cross-origin) headaches to manage.
→ Firestore as the single source of truth frontend reads it live, only the backend ever writes.
→ Firebase Auth, verified server-side on every request.
The piece quietly holding it together: a shared schema layer.
One JSON Schema, code-generated into TypeScript on one side and Pydantic models on the other, every install, every deploy.
Every arrow on this diagram is a decision, not a default what talks to what, what's cached, what's gated behind budget checks.
📌 It's deliberately not committed to version control. We committed it once and two contributors, two operating systems, got generation drift one invalid byte broke the build outright. Now it regenerates fresh every install. CI and deploy jobs run that step explicitly, because the container build won't do it on its own.
⚡ The AI call was the easy part. Everything around it was the actual engineering, Calling Claude turned out to be one of the simplest parts of the system. Everything wrapped around that one call is where the real engineering lived.
What that looks like in practice:
→ One function in the entire codebase can call Claude. Everything else routes through it.
→ Every call checks the user's AI-credit balance first. Empty balance, no call.
→ A second, independent 24-hour spend ceiling backs that up defense in depth against a runaway bug.
→ Structured data is forced through a defined tool, not regex on free text. Kills a whole bug category for free.
→ Every call success, failure, rate-limited writes one row to a cost ledger, keyed so a retry can't double-count.
💡 The design decision that made this commercially viable , Not a different model. Prompt caching.
Static instructions and per-user context ship as separate cacheable blocks. The repeated context, not the repeated instructions, is where token cost actually lives at scale. That one choice was the difference between a few dollars a user a month and quietly burning the budget. Nobody watching a demo would ever notice it. It's the whole reason LearnWise can exist, Learnwise was our personal project, we did not have endless resources to spend on it, neither was it designed to be launched commercially, it was something we wanted to share with our community.
One more cost lever: before generating anything new, LearnWise checks a goal against Firestore's vector index (via a Vertex AI embedding) for something close enough already there. Near-duplicate goals stop spawning near-duplicate content. Deploys ship through GitHub Actions via workload identity federation (no long-lived keys) nothing sitting in CI secrets to leak.
Feature branch to production: every push to main deploys to staging automatically; production only moves on a tag, and nothing in between touches a long-lived key
👉 For Us Prompt caching wasn't a nice-to-have. It was the line between a viable product and a runaway bill.
⚡ A quick honest aside on MCP , People ask if LearnWise uses it. It doesn't not at runtime. The model integration is a direct SDK call plain HTTPS, forced tool-use, no protocol in between. That's the right call for one model provider. MCP earns its keep when an agent needs many external tools through one interface, for a personal project like LeanWise, that wasn't the intent.
Where the idea did show up: one level up, in how the thing got built, not how it runs. The coding agent's value came from direct shell, git, and cloud CLI access the same problem MCP solves, just through the agent's own tools instead of a standing server. That turned infrastructure failures into fixes, instead of tickets waiting for someone to run a command by hand. If LearnWise ever needs to expose its own data to other agents, that's where an MCP server earns its place. Not needed yet.
🤝 What I actually learned: If there's a thread through any of this, it runs straight back through LearnWise itself. The whole point of what I'm building is to warn someone, honestly, before they sink hours into something likely to stall out. Stopping the agent at a clean milestone to check in was the same instinct just pointed at myself. Not a lesson. Just the same habit, showing up twice