I am a little bit disappointed with the industry right now. There seems to have been a massive unmooring of steady thought over the past year or so, brought on by the full advent of LLMs and highly competent coding agents. Every time I see a video or post about some new agentic orchestration framework, or some high throughput Jira to Agent slop pipeline, I am struck with an intense sense of deja vu. This obsession with tooling and productivity optimization smacks of engineers arguing about and ricing their terminals and IDEs, constantly looking for the holy grail of Vim bundles that will truly unlock their inner greatness. But none of this actually gets work done, none of this builds usable products, and I refuse to believe that the core role of engineering software systems is dead only to be replaced by piping customer requests directly into a coding agent. I am sure to many who previously considered themselves fine artisans it is easy to give up and start flinging vibecoded LOC over the wall1. But I cannot shake the fact that this new way of doing things just does not feel like engineering.
Our industry has (notoriously) long struggled with the concepts of rigor, systems discipline, and formal standards... often swinging violently between overdoing it and completely ignoring anything that might resemble exactness. Taking a step back, one can easily frame the recent LLM driven chaos as this finally coming home to roost, many reaping the seeds they sowed long ago. The obsession with things like frameworks, leetcode, and arcane bit twiddling2 and disregard for basic, repeatable, and structured engineering processes has set many up for a stunning disappointment. To a large extent it no longer matters how well you can wrangle that btree, or hand write a fractal-esque CTE query for Postgres. What matters is how you structure your work, how quickly you can develop a pipeline of repeatable processes, and how reliable and high quality you can make the extruded bits.
With the benefit of hindsight, this is really nothing new. I know many have believed that the reason they earned their keep was because they created beautiful experiences: SaaS that was a joy to use, pure functions with no side effects, and APIs that inspired delight. But truly, what we as an industry largely do is allow companies to turn CapEx into OpEx while presenting a throat that customers may strangle when things go wrong. This is doubly, if not triply true today. Features are now commodity, and to a certain extent they might have always been so. What is truly paramount now is how well you can operate your service. Indeed, I am increasingly of the opinion that as the next couple of years progress customers will gravitate toward companies and services that are highly reliable and consistent to use, both due to what I forsee as instability in current offerings from poor LLM usage and because feature parity will be so easy to accomplish as the cost of LOC continues to plummet. In many ways the advent of LLMs might actually bring about the programmer nirvana: where those with the most simple, maintainable, and reliable systems win.
So why, then, are we seeing such a marked decline in code and system quality from users of LLMs? How can we build highly reliable services when we are not writing the code that runs them?
Context Windows
At risk of outing myself as ignorant, I simply refuse to believe that people are submitting 25+ PRs a day3 of meaningful, maintainable, quality work. It is entirely possible that some might have codebases riddled with bugs, submitted by others, that they can then feed in an automated fashion to coding agents to generate this volume — but that then begs more questions than it answers. I also refuse to believe that people are creating maintainable pieces of software by letting swarms of agents code for hours on end, generating tens of thousands of lines of code, and then reasonably grokking that body of work. True understanding is borne in repetition and action; the ability to read voluminous amounts of code and truly internalize it into a mental model is extraordinarily rare, and not at all sustainable or scalable.
There is the argument that models will continue to get better, that we can accrue tech debt and rely on future iterations to bail us out. I doubt it. That does not mean I doubt models will get better, I simply doubt that context windows will get meaningfully large enough in the near future4. Self-attention scales quadratically with sequence length due to the matrix multiplication cost of pairwise interactions between tokens. This makes GPU memory capacity and bandwidth hard constraints on context windows5. And despite the heralding of million token length windows, my personal experience has been that regardless of model, context cliffs are a very real thing and have remained consistently within the first couple hundred thousand tokens. There is also a more fundamental asymmetry at play: as context grows a human understands more deeply, while a transformer becomes more efficacious only up to a very precise peak, after which things degrade rapidly. LLMs are unimodal where humans are monotonic. Even if window sizes did grow exponentially, that difference in the shape of comprehension does not go away.
I firmly believe that to build high quality, maintainable codebases with LLMs, tasks must be broken up into relatively small, very precise, highly structured pieces of work and fed into carefully crafted pre-shaped contexts. This approach utilizes the LLM at its most efficient and lets humans maintain their superior model of how the broader system works as a whole.
Once you truly internalize how precious context is, it becomes more and more apparent how important structuring and detailing your prompt is. Much has been said about prompt engineering and I will not waste words here, but it still seems like most of the time when I see people struggling with LLMs my answer is "prompt better". Any relevant files, relevant code locations, and relevant code structure should be explicitly detailed in the prompt — there is no reason to waste time, tokens, and context on unnecessary search or find tool calls. Even more advanced sub-agent usage with context compaction does not compare to a careful human enumeration of what is relevant to the task at hand in the initial prompt. For those who work in very large or dense codebases and are skeptical about copy and pasting lists of files every time they need to make a change, well, good news. We've created magical thinking machines that can do this grunt work for you. Additionally, unless you are building something entirely greenfield in a domain you know nothing about (and this should probably not be happening often if you are doing this as your day job), there is no reason for the LLM to be doing research or web searches when given a coding task. Any relevant information, best practices, or specific domain knowledge should either be in the prompt or moved to re-usable skills.
Remember there is nothing stopping you from using LLMs to generate a better prompt. Much of what I'm advocating for is simply to remove that work from the code generation task. If you are lazy like me it's often very productive to barf a couple sentences into a coding agent and have it do a bit of legwork, whether you do truly need some research, or just need to clarify a plan of action from disjointed thoughts. The key thing here is that none of this goes into the actual code generation task, which should always be done from a sparkling clean session devoid of unneccessary context or state, and that any prompts given to that code generation session have been fully vetted by a human to be explicit, simple, and well structured.
The better you understand the task you are asking the agent to do beforehand, and the better you explain and structure that task, the higher quality result you will get. In my experience the best prompts resemble very small one-pager design docs, are ~250-400 words6, and explicitly detail out data structures, data flow, and general code structures. In other words, the human still does the thinking, but treats the LLM as a highly competent code gen. I do not think this is absolutely necessary in all circumstances, but I do believe this style of prompting generates the highest quality code quickly and efficiently.
Prompt as Artifact
Better prompts also pay dividends with faster and higher quality code review. Code review has clearly become a major bottleneck at any organization where LLM code generation has accelerated. One of the major issues is not only the burden on other teammates to review a massively increased volume of code, but people submitting code that they themselves have not reviewed yet. By encouraging people to write more detailed prompts you create an environment where engineers are forced to think at least somewhat about the changes they are making. By increasing the initial cognitive effort, thought and care investment in the outcome increases as well. I truly believe much of the current code review problem is that people are simply no longer thinking deeply about what they are trying to do. The temptation of slinging half-thought-through agentic output over the wall is a very compelling siren song, especially with the recent increased focus on raw output across the industry. It is also undoubtedly easier to read code when you understand what that code should look like in your head before you even view it. Understanding how the problem would ideally be solved and then pattern matching that against the code you see in the diff is the most effective and fastest way to internalize what is changing, how it is changing, and if it is correct and high quality. This further reinforces the human's understanding of the system and how it is evolving, making further code review more efficient. A flywheel, if you will.
One can further increase the effectiveness of this flywheel by checking in the prompt as part of the codebase. There is no better PR description than the prompt used to create the code. This idea was widely discussed very early on during the advent of LLM aided coding but broadly dismissed, in part due to the rudimentary tooling we utilized then. Now that coding workflows have matured the idea is more in vogue, and rightly so. The shift to prompts as the human created artifact, as opposed to code, is a very important one. Once you buy into this model you realize you can treat these prompts similar to how we treated code in The Time Before:
- You can ask others to review your prompt before any code exists.
- You can use an agent to "lint" it for things like length, ambiguity, or complexity, or scan it for reliability and security concerns.
- You can include the prompt in subsequent LLM code reviews to improve the context around what has changed and why.
- You can re-run it through multiple pipelines, iterating on the original prompt and re-running until you get everything right.
- You can even re-run old prompts sequentially through newer SOTA models to continue to refine a codebase as the models themselves improve.
All of this happens upstream of actual code generation, which is exactly where the human's attention is most valuable.
The One Shot Workflow
While I don't think it's useful to think of LLM coding agents as fancy compilers, I do think that analogy is far more accurate than "collaborative coding buddy". Far too many engineers have begun to subtly anthropomorphize their agents and treat the chat TUI back-and-forth almost like productivity (it's not). Another trap is the desperate need to be multiplexing agents across terminal sessions or bizarre municipal politic cosplays, hopping between them constantly, and devising increasingly arcane ways of managing these multiplexed sessions. I'm very skeptical that increasing context switching and tripling down on multitasking somehow creates better results.
The mental model of coding agents as compilers in a code generation pipeline solves almost all of these issues. We already have a very sophisticated and battle tested multitasking strategy for combining contributions into a larger codebase: distributed source control with CI/CD. Coding should be largely automated and unseen, triggered by the addition of a prompt, curated by agent harnesses and well written skills, done by anonymous compute within a CI pipeline. The end result should just be, get this, more pull requests that either get accepted or rejected. When a PR is rejected, you don't sit in a stateful chat session trying to coax the agent into a better answer — you close the codegen PR, analyze what went wrong, sharpen the prompt, and run it again from a clean context7. All of this has slowly combined into what I'm calling the one shot workflow8. The name is a bit of a misnomer, because things are rarely a true one shot. The idea is that repeated, clean one shots driven by an evolving prompt are far more effective than iterating within a stateful chat.
The mechanics are simple. Every change starts as a prompt file, checked into a prompts/ subdirectory on a new branch — call it the prompt branch. Pushing this branch fires off the one shot workflow, which reads the prompt, generates code from a curated context, and opens a second pull request against the prompt branch containing the generated changes. The prompt branch is now your integration point: it accumulates the prompt and its generated code, keeping iterations to both in git history and providing people with as much context as they need to review or debug. If a generated PR looks good, you merge it into the prompt branch. If not, you edit the prompt, push again, and the workflow regenerates from scratch. No more stale context, no compaction, no partial diffs to wade through while trying to decide if you should ask the agent to refactor things again (you won't, you can barely grok the code as is). Only once the prompt branch contains both a satisfactory prompt and satisfactory code do you merge the prompt branch into main.
What you end up with is a git history where every change is paired with the prompt that produced it, sitting in the tree alongside the code. The prompt can be a PR description, a design doc, an example directory, or a script9. Old prompts can be re-run against newer or different models to improve code or get different approaches. Prompts can be reviewed before any code is generated, allowing for both better prompts and to increase tribal understanding of changes and how the codebase is maturing. The entire artifact graph is durable and inspectable, which is exactly what code used to be before we started ~slamming the easy button like rats in a maze desperate for another hit of cheese~ down this path.
In practice this looks like:
- Do research and preliminary work. As noted above, this often means working with Mssr. Claude and Co(dex) to scratch together an initial stab at the work, doing research, and iterating.
- Synthesize the preliminary work and research into a prompt.
- Commit the prompt to a
prompts/subdirectory on a new branch and push to Github. - Open a pull request, and fire off the one shot workflow.
- Review the new code PR the workflow has opened against the prompt branch.
- If unsatisfied, iterate on the original prompt and rerun the workflow against the same prompt branch.
- Once satisfied, merge the code PR into the prompt branch, then merge the prompt branch into mainline.
This does create quite a bit of token exhaust, but it's so worth it (and arguably not even that wasteful compared to restarting long lived TUI sessions after KV cache has expired) for the better results and faster cycle time.
-
Much of this is surely driven by emotions from purported AI driven layoffs and belt tightening, a mourning for the zen and art of coding, and a general identity crisis around who a software engineer is and what they do. On the layoffs I will say only this: I don't think there would be a similar reaction by companies if we still had sub 2% interest rates and high consumer confidence. ↩︎
-
I speak generally here, of course these things remain very important in domains like kernel optimization or high throughput network routing. Most of us do not work in those domains. ↩︎
-
That is 19.2 minutes per PR assuming 8 hours of constant work... no breaks, no pauses, no discussion, nothing but pure prompt feeding. That leaves precious little time to scroll Twitter and post! ↩︎
-
I would love to be proven wrong. ↩︎
-
These brief sentences are obviously a dramatic simplification of how LLMs operate, and I am no expert on the matter. If you are more interested you should read those who are. ↩︎
-
This is not a hard and fast rule by any means, and very often it is possible to generate high quality code with less. However one should not be surprised when even the most trivial seeming change ends up needing another paragraph or two to finely detail out testing scenarios, inputs, or things like security concerns. I've very often found good success with larger prompts, however, and usually split things up once they hit 500 words. YMMV. ↩︎
-
If this looks like a process you can automate and augment with yet even more agents, well, you're starting to believe ↩︎
-
A small plugin... for you! ↩︎
-
That's called forshadowing ↩︎