Using Coding Agents Without Producing Slop
2026-08-04
This article was first release in the german IT-magazine Golem.
When it comes to AI coding, I see the following two sentiments almost exclusively online:
The truth is typically somewhere in between. For the last 6 months, I've been spending a lot of time with AI agents, and have learned their strengths and weaknesses.
You can give an AI a task and if it's not too big, it can go on and solve it. But most useful programs are big enough that this approach won't work. AI is not good at architecture yet (at least not while coding), and once a code base reaches a certain size (maybe between 800 and 2000 lines depending on language and complexity of the program) it tends to become a mess if the AI can do whatever it wants.
However, AI can also be a huge accelerator for dev work. I prefer to view it as a kind of keyboard that types extremely fast. If you still keep ownership of the program's architecture, and review the AI's output, it can be an extremely useful tool.
The trick to getting a good architecture is to talk about it. Typically, when I start with a feature, I'll first tell the agent what it's supposed to do and give it pointers on how I think it should be implemented, and then I tell it to discuss that. A surprising number of times, it actually finds an edge case I missed, or another problem with my architecture. Then I keep refining it, or just say "Ok, good point. Your solution to that is also fine, we'll do that". Once everything is settled, I'll tell it to write a plan. This plan is typically a very detailed markdown document that can drive an implementation. Read it, because there is always room for misunderstandings, and reading the plan helps catch those. When I'm happy, and it's not already the case, I'll tell the agent to slice the plan into multiple, separately reviewable chunks.
Then I typically reset the session (to save tokens), tell it to implement the first chunk, and review it. When I'm happy, I'll tell it to implement the next chunk, and so on, until we're done. I'll also commit or squash after every step (I'm using jj instead of git, which is a very good fit here). The trick is to actually review the generated code. Often it's good. But just as often there are some small problems with it. Sometimes it's completely incomprehensible. In that case I'll start asking about the code. When this happens, the design typically missed an important invariant that the agent discovered during implementation and "helpfully worked around", resulting in garbage. When code looks strange and incomprehensible, that's the biggest red flag. Often, this will lead to further discussion and a change to the plan before I continue.
To review code, I'll first look at the diff (using the fantastic difftastic), to get an overview. For bigger code blocks, I'll typically read it in my editor, which gives me syntax highlighting and also allows me to go to definitions and understand how the new code integrates into the existing one.
During a review, I'll typically write a list of points for the agent to address. A few things I do by hand, for example: adjusting UI code, inlining unnecessary wrapper functions (that LLMs absolutely love), and similar small things.
This may sound like a lot of work compared to letting the agent run off and letting it work until you get a working implementation, but it's really not. You'll be slower with this approach for the first hour. For every project that takes longer than that, this is a huge advantage, because you keep mental ownership of the codebase. You can continue doing the architecture, which agents are bad at, and you'll be able to solve problems that you otherwise couldn't.
Coding AIs have a bunch of bad habits. You cannot completely prevent that, which is why you still need to review everything, but you can reduce it to acceptable levels. I have a project-independent AGENTS.md file in my home directory that is picked up by pi (my AI harness of choice) for every project. It's too long to embed it into this post, but you can find it here. To prevent this article from turning into an annotated version of my AGENTS.md, I'll only talk about the five most important things.
I can make mistakes. If an instruction is contradictory with the state of the project, say so instead of doing what you think I want.\
By default, an agent will basically never tell you that you can't do a thing you want
because your design is flawed. It will instead try to work around the problem, and it will
basically never get it right.The topmost coding priorities are simple and readable.\
For some reason that eludes me, this is not the default priority for LLMs.No speculative, future-proofing abstractions.\
I have so often caught an agent introducing some code where I asked it "what's this for?"
and the answer was "If we ever want to change XY, this will help". And I hate that.
If you write code like that, you will litter your codebase with unnecessary abstractions
of which maybe 1-5% will eventually pay off. Not worth it."Minimal patch" is not a goal. The goal is the smallest coherent local change.\
By default, agents will often make the smallest change they can, leaving the codebase
in really inconsistent states.No defensive error handling. Write code that fails fast and loud.\
This is a big one. LLMs love hiding errors. By default, agents write code that optimizes
for not crashing. Not for "catch bugs fast". This is one of the most important entries.
If you let an agent get away with this, you will have a very hard time finding your bugs.My actual AGENTS.md is much longer than this, but it's mostly multiple points, to make the AI actually follow the points above (plus maybe 3 or 4 other ones). To overcome strong LLM priors, you need to repeat things.
The program that runs the LLM is called a harness. The harness matters. I tried a few: Codex-cli, copilot-cli, opencode, and pi.
I kept using Pi. The three biggest advantages of Pi for me were:
I haven't written much code manually in the last 6 months. I suddenly have to look up syntactic details of more complex and rarely used constructs now and then. When I do something by hand now, I'm slower than I used to be. This isn't surprising but a little concerning. That's why I started occasionally doing things by hand again, even though it's slower, just to keep the muscle trained. Another thing is: it's tempting to tell the AI to propose an architecture, and then just evaluate what it says, instead of coming up with a draft yourself first. Sometimes I do that, because it's convenient, but I try not to do it. This level of thinking is crucial, and is what makes us valuable even in the age of AI. Finally, I think that getting a little rusty regarding coding muscle is probably not the worst thing. These days, nearly no one can write assembly anymore. Sure, many embedded engineers can read it. But writing it is a whole different story. Back in the day, everyone wrote assembly. Things change, tools change, and what competencies are important changes with them. On the upside: I got incredibly effective at reviewing code.
The hardest part of AI coding, in my eyes, is cognitive debt. You spend so little time with each part of the code that it doesn't get as much time to really sink into your memory. Coming back to a part of a codebase after a month is often already enough to make it feel completely foreign. I don't see how to prevent that. And it's also not really a new problem. If I come back to my own code after a year, it's also mostly foreign to me. The answer to that is documentation. Of course, no one likes writing it, and it gets out of sync fast, but that's where the agent is handy again.
As soon as a project has reached a level of complexity that is worth documenting (which is a low bar), I'll tell the agent to create a file to document it for itself. Then I'll add an entry to the local AGENTS.md that tells the agent to read that file before answering the first request and to keep it up to date when it makes changes.
The resulting file is actually also very readable for humans, and if you have any questions about the code, agents are typically very good at answering those.
Rebuilding a code base in your head is one of those competencies that is going to become much more important in the future. But the good news is: practice helps.
I see a lot of debate around this. AI comes with MANY problems: It puts a huge burden on the internet (traffic), consumes insane amounts of energy, makes hardware prices rise sky high, destroys jobs in many areas (especially around art, writing and programming), is trained on stolen data, makes producing garbage cheaper than ever, and is a terrific tool for malicious actors to create even better bots to control important information exchange platforms like reddit, X, etc.
Those are BIG problems. But at the same time, I'm not going to reject a huge advantage dangling under my nose. This is Pandora's box, and I don't think you can afford to not use this tool as a developer. This is not a justification, this is just my personal compromise.
I'm aware this is only a short discussion. I felt like I had to mention it, but it shouldn't be the main point of the post.
AI is here to stay. It's being adopted rapidly, and it promises huge performance gains for those who are able to use it properly. However, this obviously requires you to know what "good" code looks like. If a beginner tries to do this, they will probably just approve of bad code. AI can accelerate experienced developers, but it can also let beginners bypass exactly the struggle through which that judgment is developed. By default, it's a huge slop-machine, but it can be tamed. And by discussing all my designs with an AI, I actually produce more stable code at a much higher speed. It's not clean, there is a large dark side, but at the same time, it is an insane accelerator, and I really enjoy the kind of hobby projects it enables for which I previously didn't have time.