In search of coding productivity

I haven't been a full-time software engineer for many years, but I have kept most of my senior software engineering sensibilities. I know what good code looks like, and how it should be architected. I'm pretty great at debugging and have been scarred by so many poor implementation choices over the years that I have a built-in early warning system for many of the engineering anti-patterns that crop up. Despite this, I no longer have muscle memory for any specific languages or frameworks. I still code in javascript, ruby and a couple of others, but truthfully I've regressed to the point of having a pretty sizeable taste gap1.

The advances in LLM-driven software engineering over the past 18 months were a boon for people like me. I could once again be productive by guiding an LLM in an engineering symbiosis. I would prompt the LLM what to write, and then I'd spend a while fixing the various mistakes it made. Rinse/repeat and eventually we got to working code. It was a bit of a slog, but I was fairly productive again. Truthfully, I didn't really enjoy being a code reviewer like this, but I assumed it was a stop-gap. I expected that after doing it for a while, I would get the muscle memory back and find myself needing to lean on the LLM less and less for code generation.

And then about 3 or 4 months ago, LLMs became really good at writing code. And then they became capable of reasoning towards a solution. Suddenly software engineering was in the middle of an existential crisis.

The internet's answer to this has been to go all-in and let the LLM write whatever code it wants in response to the prompts you give it. In return you get to enjoy a 10x productivity boost. You're probably already aware that Andrej Karpathy coined the term vibe-coding2 for this practice. Karpathy explains that he “just sees things, says things, runs things, and copy-pastes things.” You give in to the vibes, and ignore the detail of what the LLM is doing.

Why I don't enjoy vibe-coding

Yes, vibe-coding can produce code fast. But it breaks the basic engineering loop: understand what’s happening, make a hypothesis, precisely execute it, and verify the result. This is what engineers do! You could give me a 10x productivity boost, or even a 100x productivity boost and I'm still not going to feel satisfied. If I never feel mastery, if my job boils down to cross-checking outputs from a text predictor, then we’ve basically outsourced the joy of engineering. Needing a specific set of skills and experience in order to get good results is exactly what makes an endeavour feel worthwhile to me!

Vibe-coding produces software without the engineering — and because of this, I find it immensely unsatisfying. At a psychological level it isn't giving me what I need as an engineer:

  1. I never create a mind-palace of how the system works, so I never feel in control of a solution.
  2. I can't get into a deep flow state, because I’m constantly stopping and starting, waiting for code generation, or fixing what the LLM has done.
  3. I have to constantly guess what context the LLM needs to keep it on the rails. It feels like intellectual whack-a-mole.
  4. It doesn't feel like I'm on a pathway to mastery.

From an engineering standpoint, it’s not just about speed and happiness; it’s about designing systems we can understand and maintain. Vibe-coding as an approach means we’re left with no design record in our repository. I'm pretty sure people are not checking in their sequence of prompts as a design spec. Even if they did, there's no guarantee that repeating the sequence of prompts would recreate the same result. The code that does get checked in is not a direct reflection of the engineer’s brain — it's an interpretation of a prompt record that no longer exists. It's not just ephemeral, it’s unmaintainable.

Not just for fun

Today's answer to these limitations is to claim that vibe-coding is only suitable for fun and prototypes, not for serious work. Folks who have already integrated LLMs into their workflow (like Simon Willison3 or Harper Reed4) show that experienced engineers can get high-leverage with forms of vibe-coding, but the prevailing wisdom in the industry seems to be that for serious codebases, the code that gets spat out should be understood on a line-by-line basis. A form of “LLM plus rigorous engineering” which puts engineers back into the baby-sitting role.

I disagree with this assessment. I'm willing to take a bet today that humans writing high-level programming languages will become incredibly niche. I suspect that the age of high-level languages is ending and they will follow in the footsteps of the low level programming languages into obscurity. But that doesn’t mean we should skip straight to “prompt and pray.” I think we need a new paradigm in software engineering that lets us embrace LLMs generating all the code, but does so in a way that will let us continue to do software engineering.

Blueprint-driven development

I’ve been experimenting with what I call blueprint-driven development. It’s a paradigm that tries to salvage the best parts of LLM-assisted coding while preserving engineering discipline. Concretely, I’m writing a blueprint for each module, and then telling the LLM to exactly implement the code according to the blueprint with zero permissible deviation. A blueprint could in theory be anything from a series of prompts, a structured domain-specific language, or a diagram, but the thing I wanted to achieve was the right balance between formal specification and architecture, and the freedom for the LLM to implement the code in whichever way it wants.

The first version of the grammar I came up with was as follows:

Module: [MODULE_NAME]

Purpose:
  [Free-form description]

Interface:
  [DEFINITION] -> [RETURN_TYPE]

Datatypes:
  [TYPE_NAME] -> field1:type1, field2:type2, ...

DataFlows:
  [SOURCE] (<-|->|<->) [TARGET]

Constraints:
  [SUBJECT] -> requires([CONDITION]), ensures([OUTCOME])

Specification:
  [Free-form description]

I don't think this is perfect, but it has some really nice characteristics that are worth discussing. The first thing is that I can construct a mind-palace of what’s going on in the codebase — I can tell it how I think the implementation should be achieved in free-form text, but I can mix it with a tightly specified external perspective: how modules interact, what data flows in or out, the constraints that shape the solution. Secondly, when I'm working on these blueprints, I can get closer to the flow state of iterating through an implementation in my head and on the page, and I get to delay the stop-start babysitting phase of LLM coding till later. Thirdly, when I do start to ask the LLM to implement a blueprint, I can ask the LLM to audit its compliance against the blueprint. This turns out to be immensely more satisfying (to me) than QA-ing the output looking for weird bugs. I’m no longer stuck in a purely reactive stance, wondering if the AI has done something bizarre. Fourthly, I'm producing an artefact that I can check-in to the repository which not only acts as documentation, it also serves as the baseline for future maintenance or re-factoring5. Finally, it makes me feel like I’m back on a path of mastery. I hypothesize a good solution, specify it, implement it, and test it. If I get good at this process, I'm getting better as an engineer. The fact that the grammar mixes free-form and strictness helps with this feeling. I know it makes no difference to an LLM today, but I can imagine a future where an LLM enforces strict compliance to the grammar to remove ambiguity. Strict syntax compliance turns out to be something I like about programming (who knew?)

Blueprint inception

I decided to dogfood the concept of blueprints by using blueprints to write a blueprint lexer, parser and language server. The goal being to syntax highlight blueprint files in VSCode:

As you can see, it works! Truthfully, there was quite a lot more iteration at the implementation stage than I had expected, and I had quite a bit of exasperating LLM correction still to do (plus one revert of two hours' worth of work that just got worse and worse) but it definitely felt better than the previous approaches I'd used. I also felt in control and productive. Maybe not at the levels I keep hearing people brag about on their internet highlight reels… but definitely on a productivity pathway I'm excited about. I also now have a record of why the codebase is implemented the way it is. Instead of random prompts or ephemeral chat logs, the blueprint sits in the repository, right alongside the code. It’s basically a textual system diagram. It means that if I come back in six months I can see the design’s essence without rummaging through LLM conversations6.

Reclaiming the joy of engineering

Ultimately, vibe-coding might go on to win the future of coding. It’s fast and it's only going to get better. But the intangible costs — feeling out of control, missing a sense of mastery, and losing that precious design artifact — are a steep price of entry. I'm not sure my mental health is up to it.

Blueprint-driven development is my first attempt at bridging that gap: LLM-powered coding, guided by a human architect who never loses sight of the big picture. It’s definitely not the final end state but I encourage all the software engineers out there to experiment in this space — already I think it's materially more fulfilling than vibe-coding.

And that’s what engineering should be about: not just shipping, but shipping with purpose.

Try out blueprint-driven development. Experiment with a small module. Copy and adapt the grammar, fill in your Purpose, Interface, and DataFlows, and see if you can drive your LLM of choice to implement it. You don't need syntax highlighting to use these — you can just give the blueprint to an LLM and tell it that it must implement it exactly. Giving it the grammar may help it to understand better, but I suspect it isn't required. Just be very strict when you are asking it to make further changes. Your cycle should ideally be: adjust the blueprint -> implement the blueprint… but it's also fine if you eventually drift a bit from the plan. In that scenario, just ask it to audit compliance with the blueprint and then tidy up anything that has gone awry. Honestly, it's not really that important how you do it so long as it gives you a feeling of being in control while letting the LLM generate the code. Once you've gotten the codebase where you want it, commit your blueprint to the repo. Let future-you (and your teammates) see the why behind your AI-generated codebase.

Because at the end of the day, the best code is the code we understand — and vibe-coding just isn’t going to give us that.

Let’s get back to engineering!


  1. https://vimeo.com/85040589
  2. https://x.com/karpathy/status/1886192184808149383
  3. https://simonwillison.net/2025/Mar/11/using-llms-for-code/
  4. https://harper.blog/2025/02/16/my-llm-codegen-workflow-atm/
  5. In practice, I found that I need to add unit tests too to catch regressions when asking the LLM to reimplement a blueprint.
  6. Unless I'm missing it, I don't think this is possible with Github Co-pilot.