Agentic Development
July 20, 2026

Misadventures in Agentic Development
What creates a good agentic developer?
Hard to say, but these observations might push you in the right direction.
Going from A -> D
Models like to take shortcuts, this creates a pattern in agentic development where models jump between A -> D, if you are an experienced agentic engineer you will often have found that this method is not sufficient. When it comes to developing complicated features for larger codebases the model's native workflows will fail.
This is most likely not an intelligence problem. This is a workflow problem. You need to develop a way of working that fills the vacuum in the middle and designs and builds out features by taking you from A -> B -> C -> D.
A clarification is that the SOTA models will not have any struggles working on a codebase that is smaller than 20-35K LOC. That limit has been solved by the raw intelligence the models produce, and at that point the tech debt is too small to matter.
But with larger projects you ask the model to implement something complicated, it comes back and it is 40% there. Looks great on the surface. You use it, you spot a bug, you prompt it to fix the bug, and it adds an if statement. You spot another bug, another if statement. The percentage creeps up. 50, 60, maybe you fight your way to 70% fully implemented. Then you can no longer "vibe fix" your way around the issues, because if your system is complicated and not a surface level CRUD application, the changes the model introduces will break something buried in the code. It becomes a vicious cycle. And the prompting to even get to this point has been miserable and rage inducing. All of a sudden you are playing whack-a-mole with a codebase that's slowly turning to shit.
LLMs try to jump to the finish line.
The example
A side project of mine is Agent Code. An AI based IDE that is a UI and feature middleman for Claude Code, Codex, etc.
The core thesis is recreating the 1x1 native experience, hence the decision to not use the SDKs but to build our own headless packages for Claude Code and Codex, so we preserve native features such as compaction, all the commands, etc.
At one point we had solved the hard upstream problem: producing a clean, sanitized object that represents the conversation, basically the object for what to put on the screen for an agent. Call that A. A looks like this:
What A actually looks like
{
"rows": [
{
"candidate": {
"id": "entry:a1f3c8d2-01",
"owner": "committed",
"provider": "claude",
"contentKind": "user-text",
"timestampMs": 1753027845123,
"textKey": "run the test suite and fix whatever breaks"
},
"order": { "sequence": 0, "timeMs": 1753027845123 }
},
{
"candidate": {
"id": "sem:msg_02XYZ:0",
"owner": "semantic-current",
"provider": "claude",
"source": "proxy",
"turnId": "msg_02XYZ",
"contentKind": "assistant-text",
"timestampMs": 1753027852310
},
"order": { "sequence": 3, "timeMs": 1753027852310 }
}
],
"decisions": [
{ "candidateId": "entry:a1f3c8d2-01", "selected": true, "reason": "selected" },
{
"candidateId": "sem:msg_01PREV:0",
"selected": false,
"reason": "committed-text-owned",
"suppressionOwnerId": "entry:b2c4e9f1-02",
"evidence": ["exact-text-match", "committed messageId == turnId"]
},
{
"candidateId": "ghost:g-msg_01PREV-0",
"selected": false,
"reason": "ghost-superseded",
"suppressionOwnerId": "entry:b2c4e9f1-02"
}
],
"unknowns": []
}
What was left was to take that object and actually paint the React elements. Call that D.
Image of a rendered feed
This was very hard, because the ownership system only produces a sanitized object and does not categorize the different shapes, nor lock us into a fixed list of things to render. So the actual set of elements we had to draw, and their shapes, was unclear. Claude has different shapes for a bash command, a file edit, and so on, and all of it has to be rendered cleanly and well. Same goes for Codex, and for any other provider (OpenCode, Pi.dev). The basic shapes are easy but the edge case shapes become tricky.
So what happens if you just point the AI at this and say "render the feed"? You get a feed.tsx. It handles the shapes that happened to be in its context. That is your 40%. Then you spend a week prompting bugs, it adds if statements, and you crawl toward 70 before the thing collapses under its own weight.
That "vibed" implementation captures the most common rendering shapes great, but I constantly have to tweak it, and when it comes to the less common elements all of that is just unsupported. This is the result of taking the A -> D shortcut.
What needs to be done is to step back and see it from a bird's-eye view. Each time I have a problem like this it ends up following the same development flow. Fixing the rendering went like this: before implementation, we built a recording feature that captures every shape and how often it occurs while I use the app. Then, off the back of that, a clean shapes.ts for every provider that breaks down every operation, its shape and its data. Only once that existed did we start implementing, one shape at a time.
That is A to B to C to D. A is the feed object. B is the recorder. C is the catalog. D is the actual rendering.
Our workflows reward the models for output, so without guidance the model will always try to take the shortcut. This is one of the domains where human intervention in agentic development still shines the most: taking the decision to do something slower in order to do it correctly.
The model optimizes for producing an output you can see.
A recorder renders no element on screen. A catalog renders no element on screen. To a system sprinting toward the visible goal, the entire middle of the road looks like empty air, so it just drives through it. The model optimizes forward from whatever structure already exists. It will squeeze the next fix into that structure forever. What it will never do on its own is stop and say "this whole substrate is wrong, we need to tear it out and build the boring invisible thing first." That sentence requires calling the current work a dead end, and forward-patching never has to make that call.
The exact same development cycle we used to resolve the rendering applied to the step before it.
Before the rendering, we had the ownership problem.
This part is Agent Code specific, but getting to that sanitized object I referred to as A before was a very hard challenge on the same granularity as the rendering.
The background is that Agent Code runs a simulated terminal (PTY) with Claude Code, and we are replacing the terminal rendering in React. For that we need multiple data sources, multiple "channels". For example, we can use a proxy to read the Claude Code API requests to Anthropic in real time and use that for the streaming. But the source of truth for agent conversations is the JSONL files that Claude Code and Codex store for transcripts, and on top of that we need screen parsing to properly react to user questions and permission prompts. So we have 5 channels. When I was letting the model make the architectural decisions, we ended up with these 5 channels fighting each other in the rendering layer, creating days of bugs with duplicated messages, weird disappearances of conversation entries, and so on. What actually solved it was completely isolating the conversation object from the rendering and establishing a proper ownership based system that produces the final sanitized object of exactly what is going on and what should be shown on the feed. And, recognizing that because this ownership system is complicated, it needed to be TDD and based on real fixtures from real collected data.
Same pattern as before. What broke the loop was stepping in and saying: we are redesigning this from the ground up. Isolate the hard part. Build a test driven system with real fixtures, and just produce one clean sanitized object in isolation that we then hand to the next step. Both times I had to establish about 50% of the groundwork for the refactoring plan myself, because the model simply could not arrive at that structure from where it was standing.
AI's toxic relationship with TDD
Both of the Agent Code problems above were basically fixed by TDD, and TDD is great. AI based TDD is shit. It is one of the reasons I am so vocally critical about superpowers and gstack.
For both of the problems above the solution was "test driven with real fixtures." Models love TDD. They love it because it is a vanity metric that creates the illusion of a stable codebase. A model will more often than not write TDD with tests it already knows are going to pass. It decides what the code does, then writes tests that bless exactly that, and hands you a green suite that proves nothing except that the code does what the code does.
The failed rendering where the AI took the shortcut, the one I referenced earlier, passed 481 out of 481 tests. All of it was bullshit. The tests were green and the product was broken, because the tests were written from the same imagination that wrote the bug.
Test driven is actually one of the angles where human input matters the most, because the complicated nature of it means you often need a pretty clever and fundamental understanding of your own systems to write proper tests and produce the proper fixtures for the most complicated and integrated features you are building.
Structure
Human input on structure is one of your main jobs as an agentic developer.
IMO, the most important principles for an AI scalable codebase are modularization and isolation.
Important context: the definition of vibecoding I'm referring to is having zero knowledge about structure and design principles, where fixing an issue is just you describing to the model the symptoms you see on screen. Agentic development is when you are the engineer guiding and deciding on the structural patterns, but you do not write the actual LOCs yourself.

This illustration that I created is one of my favorites when explaining agentic development to people. The left represents a vibecoded codebase, and the right is what an agentic developer should do. The point is that if you vibecode, the codebase becomes a living organism. All parts of the code tree import and stretch over all the other parts, and you end up with a feature that touches 40 out of 100 files. If you are a serious agentic developer, your main purpose, because of how good the models are on the granular level, is to untangle as the models work. I wrote 70k LOC as the mark because that is roughly the theoretical limit of how large a codebase you could survive just vibing around. You would have created a nightmare well before that, probably at the 30k mark, but maybe you could latch on with life support until 70k.
Some background on Agent Code for reference: the codebase is currently around 300k lines.
And this is not some valley "circle jerk" about LOC being a measurement of productivity. A lot of the engineering time has actually been spent reducing the LOC, and if I hadn't, the codebase could easily be well above 800k.
What I am trying to emphasize is that Agent Code has grown to be pretty complicated. It's a full AI based IDE with a lot of features. The point of Agent Code was also to serve as an experiment: it has 0 human written LOC, and the majority of the code is not human read either.
Most of what I do is give the agents structural direction, and of course tell them what features to implement. That structural direction is basically the whole untangling part, and it goes perfectly back to the rendering and ownership issues. Before I stepped in, those features were spread like weed over ten times more files than they had to be, creating a mess where a change in one file broke a completely different domain in the application. If you are going to have a large codebase written by AI, you need to make sure the complicated features are as isolated as possible, both in code and directory-wise. That is what lets you introduce changes and build out new stuff without piling onto the mountain of tech debt.
Prompt engineering
A final point of this rant is that prompt engineering is a misunderstood concept. There is a large group of people (maybe the majority?) that believe making your agents perform well is about what they call prompt engineering, where their definition is that you are supposed to do junk like writing "you are a senior software engineer" at the start of your prompt. That is useless. The truth is that it does not matter much at all how your prompts are formatted in that regard. What prompt engineering actually is, is just system design in disguise. Now, a clarification: if you are adding a new feature, the more context you give about it the better the results will be. But the format of that context (the prompt) does not matter. The prompting that matters, which ties back to my thesis above, is steering the AI toward the right refactoring and structure. For example: "It feels like our rendering system is spread out over the codebase, should we not take this and isolate it into its own /rendering directory?"
Building a great codebase and a great product with agentic development is just a chain of observations from you and prompts like that to the model.
This way of thinking has been turned into an agent skill I use during development.
GitHub Repo