Essay
11 min readBuilding Faxter · Part 2Vibe Coding with Claude Code: What a Year Inside the Loop Taught Me
What shipping production software almost entirely through AI coding agents actually looks like — and the rules I now refuse to break.
- Engineering
I have been writing software with LLMs in the loop for about two years now. The first year was Faxter. The second year has been Clerk. The difference between those two years is so large that I have trouble explaining it to people who weren’t paying close attention, and I think the difference is worth writing down, because I suspect a lot of founders and engineers are still calibrated to what the first year felt like and have not yet updated to what the second year actually is.
This essay is about that update.
Year one: vibe coding as glorified autocomplete
When we were building Faxter, “AI-assisted coding” meant something specific and limited. You had a chat window open in one monitor and your editor in the other. You described what you wanted. The model gave you a block of code. You read it, understood it, usually rewrote half of it, and pasted the rest. Then you debugged, because the model had hallucinated an API that didn’t exist, or used a library version from two years ago, or confidently produced something that would compile and run and silently corrupt your data.
It was useful. It was genuinely useful. I built, largely alone, a working public cloud platform — virtualization, billing, the control plane, the whole software stack — at a speed that would have required a real engineering team a few years earlier. But the human was doing all the thinking. The model was a faster way to produce boilerplate and a pretty good rubber duck for design questions. It was not, in any meaningful sense, a collaborator.
The mental model I had then was: the AI is a very fast junior who cannot be trusted with anything load-bearing. Every line it produced passed through my head before it reached the repo. That was the correct mental model for the tools available. It is not the correct mental model anymore, and the fact that a lot of smart people still hold it is why I keep having the same conversation at dinners.
Year two: the model can hold a problem in its head
Sometime in late 2025 and through the first quarter of 2026, this changed. Not gradually. The models stopped producing snippets and started producing work. You could describe a feature, point at a repository, and get back a change set that understood the existing code, touched the right files, matched the conventions, and ran. You could ask for a refactor that spanned a dozen files and get it done in a single pass. You could say “the bug is somewhere in how we handle VAT on imported goods” and come back twenty minutes later to find it diagnosed, fixed, and tested.
The thing that had changed was not just code quality. It was duration of coherent reasoning. The old models lost the plot after a few hundred lines. The new ones could stay on task for a whole feature. That sounds like a small difference. It is not. It is the difference between a tool and a collaborator, and everything downstream of that distinction — how you plan work, how you structure a codebase, how many people you need, what a “team” even is — changes.
I started building Clerk right as this shift was happening, and the journey from the first commit to where I am now is the clearest picture I have of what vibe coding actually is in 2026.
The first architecture was a mistake. So were the next four.
I started by trying to build an agent from scratch. This was a mistake, but it was the kind of mistake you have to make in person to understand. I had opinions about how an AI agent should be structured — planning layer, execution layer, orchestration layer, response formatter — and I built all of it by hand. It worked, in the sense that it ran. It did not work, in the sense that every new feature was harder than the last one, and I spent more time maintaining my own scaffolding than I spent on the accounting logic that was the actual point.
So I threw it away and tried a second design. Then a third. Then a fourth. Each one was a different theory of how to decompose the problem — a tiered planner that routed simple queries to cheap paths, a composite-operations framework that let me define workflows declaratively, a three-stage response pipeline, a dual-agent system where a custom orchestrator handled the cost-sensitive cases and something smarter handled the rest. Each of these sounded like a good idea at the whiteboard. Each of these was, in retrospect, a version of the same mistake: I was building a framework when I should have been building a product.
The fifth attempt was LangGraph’s ReAct pattern. It worked on the first try. Not because LangGraph is magic, but because it stopped me from building the scaffolding I kept insisting on building. ReAct is almost embarrassingly simple: the agent reasons, picks a tool, acts, observes the result, reasons again. That’s it. There is no planner, no orchestrator, no tiered router. The model handles complexity natively because the model is actually capable of handling complexity now, and every layer I had been lovingly constructing around it was just a cage that constrained what a capable model could do.
This is the single most important thing I have learned from vibe coding in 2026, and it is counterintuitive enough that I want to say it plainly: the better the model gets, the less software you should write around it. Every abstraction you add is a bet that the model is dumber than it actually is. That bet kept being wrong. It kept being wrong faster than I could take it off the table.
The clean restart
By early 2026 the codebase was an archaeology of good intentions. Four agent architectures’ worth of dead code, two parallel agent systems, a 2,500-line response formatter, a capability registry with forty entries, composite operations defined in JSON-like structures, hardcoded tax rules, template-based responses that felt robotic, and a planning layer I could no longer justify. It worked. Users used it. But every change was painful, every bug fix risked breaking something else, and onboarding a new contributor — even an AI one — was an exercise in forensic anthropology.
I did something I had not done before. I asked Claude Code to read the entire project and summarize, in brutal detail, what we had learned. What worked. What didn’t. What we’d do differently. What anti-patterns we kept falling into. No diplomacy. No face-saving. Just the lessons.
It produced ten articles’ worth of prose. I read it twice. Most of it was things I already knew but had not let myself say out loud: the dual-agent system was fundamentally flawed because users needed one way to do things, not two. Templates were a mistake because every response felt generic when it should have felt like a conversation. Accounting rules should have been data, not code, because tax laws change and jurisdictions differ. The “just one more layer” instinct had cost me more than any other single habit. The 80% of features half-finished were worth less than the 20% finished to 100%.
Then I did the thing you are not supposed to do. I started over. I used the ten articles as the prompt. I told Claude Code to build Clerk again from scratch, with those lessons as hard constraints, and I sat with it for a week.
The new codebase was done in seven days. 100% AI-generated code. Not “AI-assisted with heavy human rewriting.” AI-generated. I read every diff, I made decisions at every fork, I redirected when it went off course, but I did not write the code. I directed the code. The distinction matters.
The rebuilt system was smaller than the one it replaced, cleaner, faster, and — this is the part that still surprises me — more capable. The discipline of the lessons-learned prompt forced it to refuse the abstractions I had been too attached to delete by hand. A human rewrite would have smuggled half of the old complexity back in out of sentimentality. The AI had no sentimentality. It just built the thing.
What I actually do now
People ask me what “vibe coding” looks like in practice in 2026, so here is the honest version, from the inside.
I spend most of my time doing three things: deciding what to build next, reading diffs, and arguing with the model when its first instinct is wrong. The actual typing of code — the thing I used to think of as “programming” — is now the smallest part of the loop. Most days I don’t touch the keyboard to write code at all. I touch it to write prompts, to read what came back, and to say “no, do it this way instead, and here is why.”
The skill that matters is not prompt engineering in the sense of magic incantations. It is taste. It is knowing, when the model shows you a diff, whether the diff is right. It is knowing which of the four reasonable-looking approaches it just proposed is the one that will compound into a clean codebase six months from now versus the one that will metastasize. The model can generate five plausible architectures in the time it takes you to read one of them. The value you add is picking the right one and refusing the other four, fast, with conviction.
The other skill that matters, and that I did not expect, is refusing to intervene. When you are used to writing code by hand, watching an agent work is excruciating. It goes down paths you wouldn’t have taken. It tries things you know won’t work. Your instinct is to stop it, grab the wheel, and do it yourself. That instinct is almost always wrong now. Most of the time the agent recovers. Most of the time its path, even when it’s not the one you would have taken, arrives somewhere fine. Every time you interrupt, you are trading a small amount of its momentum for a small amount of your control, and the exchange rate is worse than it feels.
I have had to unlearn the habit of mid-air corrections. I try to set the agent up well, let it work, and review the result. If the result is wrong, I tell it what was wrong and let it fix it. The temptation to micromanage is strong and almost always counterproductive.
The rules I now refuse to break
After two years of this, I have a short list of rules that I will not violate, because each of them was earned by ignoring it and paying for it:
One way to do things. If your system has two agents, two codepaths, two ways to reach the same outcome, you will spend the rest of the project’s life paying for the choice. Pick one. Delete the other. If you can’t decide, flip a coin — being wrong is cheaper than being split.
No templates for anything the user reads. If a human is going to read it, a model should write it. Templates feel robotic because they are robotic. The entire point of an AI-native product is that the product can talk. Don’t gag it.
No hardcoded rules for anything that changes. Tax rates change. Jurisdictions differ. Accounting standards evolve. Anything that lives in the world, and the world gets to modify, belongs in a knowledge base the model can read — not in an if statement.
The minimum path from API to database. If your request diagram has more than four boxes, you have too many boxes. Every layer is a bet that you are smarter than the compiler and the model combined. You are not.
Read the lessons before starting work. I keep a LESSONS-LEARNED.md file in the repo and I load it into context at the start of every serious session. It is the cheapest insurance I have ever bought. The anti-patterns you fell into last month are the anti-patterns you will fall into this month if no one reminds you.
Let the model finish. Whatever is happening, let it finish. Then judge. Interrupting is almost always worse than reviewing.
Finish the 20% before you touch the 80%. At every stage of Clerk I have had the temptation to start the next shiny feature before the current one is actually done. Every time I have given in, I have ended up with another 80%-complete feature in a codebase that already had too many. Finish. Ship. Then expand.
What this changes
The question I get asked most often is whether this is real leverage or a party trick. Is a solo founder with Claude Code genuinely equivalent to a team of five, or are we all going to wake up in a year and find out the code we shipped at 3x speed is unmaintainable garbage?
My honest answer, after two years of actually doing this: it is real leverage, but it is leverage that rewards a specific kind of operator. If you have taste, if you can read a diff and know whether it’s right, if you can hold a product vision in your head and steer toward it, if you can resist the urge to micromanage and also the urge to trust too much — then yes, the multiplier is large, maybe larger than people are willing to say in public. I have shipped more working software in the last year, alone, than a funded team of engineers would have shipped on the same problem a few years ago — and the difference is not me. The difference is the tools.
If you don’t have those things, the leverage is still there, but it cuts both ways. You will produce more code, faster, and some of that code will be wrong in ways you cannot see, and by the time you find out the pile will be too tall to excavate. The failure mode is not “AI writes bad code.” The failure mode is “human who cannot evaluate AI output accumulates bad code faster than ever before.”
The thing I believe, and that I am betting Clerk on, is that the people who learn to do this well will have a window of a few years where they can build entire products that would previously have required entire companies. Not because they are smarter. Because the cost of converting a decision into working software has collapsed, and the bottleneck has moved upstream to the decisions themselves.
That is what the year of vibe coding with Claude Code taught me. The bottleneck moved. You either moved with it, or you are still writing code by hand and wondering why the people who didn’t are shipping so much faster.
I know which side I’m on. The next essay is about what you build when you’re on that side — and how the tool you built to run your own business starts turning into a business of its own.
