I've been a (relatively) late adopter of LLM-assisted coding. I shied away from the OG Copilot on ethical grounds. I've contributed a wee bit to open source and it really got under my skin knowing that our open source contributions were used to train proprietary models, which were then packaged into a service that was being sold back to us. My editor of choice is VS Code and while it's wonderful, the aggressive nature of Copilot also initially put me off. However, like most people who sling code for a living, I found LLM-assisted coding simply too big of a juggernaut to ignore and as the saying goes:
If you can't beat 'em, join 'em!
In all fairness, I was also lucky in that we had a bit of a shake-up at our organization and I was introduced to a few different folks who showed me their workflows. The productivity gains provided practical value that trumped my ethical concerns, which were fairly weak to begin with anyway. It's not like I've authored Redis or anything like that.
In general, I like to think of AI-assisted work as falling into three buckets based on how costly an error would be and how reliably the output can be verified:
- Disposable
- Consequential but independently verifiable
- High-stakes or difficult to verify
Producing funny images for a social media post very clearly falls into the high-stakes category and reviewing code for an access-grant module that determines whether patient demographic data is visible or not is a fairly simple task that an LLM can manage without oversight. Just kidding, but you get the point. The devil is in the details and the determination of whether verification is reliable and cheaper than doing the work manually is based on your requirements. However, this raises the next question: if the task has been determined to be critically important, how best to go about it with AI assistance?
In my experience (and apparently also a fair number of Hacker News readers), one gets the most out of AI output when one is already an expert in that domain. The LLM agent can then be used to do the grunt work and the domain expert can use its assistance to improve the output. The jury is still out on the productivity gain here but if my late-night social media browsing is any indicator of a scientific judgement call, I'd put that at exactly 24.3%.
What does that mean? Let's take a personal example: when I first started researching FHIR with LLM assistance, the model's answers presented FHIR as a modern approach to healthcare interoperability but gave me little context about adjacent approaches such as openEHR, or about the different problems they are designed to solve. Now, were I not in the business of healthcare data modeling per se, I might have come away believing that FHIR was the greatest thing since sliced bread.
In modern parlance, without domain knowledge you're simply vibing: judging whether the output feels right rather than whether it is right. A first attempt with a frontier model will get you almost there but the iterations needed for the last 20% reduce coherence and introduce complexity if not checked. This polished but incoherent result is what people perceive as "AI-slop".
In terms of software development, we've seen the rise of the vibe-coder, someone with deep domain knowledge who was previously stuck needing technical assistance to bring their ideas to life, who now can unleash their creativity and energy. This is a very good model and it works well for a certain scale because of the above corollary: anything that can be verified works. Note that while subject-matter expertise helps determine whether software solves the right problem, software-engineering expertise is still needed to evaluate its architecture, security, operability, and maintainability. Thus, as the system grows into domains the creator cannot evaluate, local inconsistencies can accumulate into spaghetti code. A few of the reasons that purely vibe-coded projects are subject to this phenomenon are:
- Context drift: Each change may be locally reasonable but inconsistent with the wider system.
- Accelerated debt: Increased output allows a small bad decision to spread quickly.
- Addition bias: AI agents are biased towards producing their own code. Unless asked, an AI agent will not necessarily research and suggest an existing library, abstraction, or simpler alternative.
- Local optimization: Agents may patch around a problem instead of reconsidering the underlying design.
The larger the task, the greater the opportunity for these effects to compound. So what can be done? Short of waiting for the next wave of agentic-AI-distributed-big-data to solve everything for real™, here are some practices that help both teams and solo developers:
- Small merge requests: This was a hard-earned lesson. We had instituted a 4-eyes principle but the rate of change was so high (as well as the amount of code) that we resorted to simply asking the LLM tool to review its own work. It usually found one or two problems but always gave itself an A+ :). We did not try running agents from different providers against each other mostly because we only have one company plan. AI review does not substitute for human review and smaller merge requests keep meaningful human review tractable.
- Context documentation: If we're being honest, this is actually the hard part. In the old world, we would just start building and pivot as and when required. This was called being agile. Strangely enough, we can move so fast now that all the work has shifted back to the front. We need to figure out what we want, especially regarding terminology, general principles, practices — and write it down for both people and agents. Where possible, enforce them separately through types, tests, linters, and CI.
- Architectural decisions: An important part that is often overlooked is that architectural decisions require explicit review and a discussion within the team. A small decision can turn into a system-wide convention very quickly.
- Communication: Agents accelerate individual output, but they do not create shared understanding. Teams still need to discuss intent, assumptions, and trade-offs rather than allowing generated code to become the conversation. I've seen people who loved to discuss shy away and let their agent do the talking now. Maybe it's because we prefer conversing with our agents?
- Model-vs-model: The jury is out on this one but I've included it here for completeness. The idea is to get one model to review the work of another, ideally telling it the work was done by a competitor, to unleash the beast, so to speak. However, the models are trained on similar data and have similar biases, not to mention the costs involved (see also: one company plan).
When the first wave hit, I was afraid for my career and wondered whether my skill set had become obsolete. I no longer see these tools as replacing software expertise. Like compilers and higher-level languages, they move the bottleneck: producing code becomes cheaper, while deciding what to build and verifying it become more important. They help us build faster and, unless we are careful, make mistakes faster too. I'm happy to use them, and excited to see what comes next.