The Cost of Letting AI Write Faster Than I Can Think
2026-09-06
Last couple of years I have come to use AI coding tools as part of my workflow. As a direct result of that, in just over roughly five months since May, I have read more than six million words of generated material related to programming. Most of my experience has been with Aider, Hermes, OpenCode, and different GPT, Claude, and Kimi models. More importantly I used them on projects that continue for weeks and months, where the architecture changes during implementation often several times, the assumptions have to be revised, and decisions made in earlier stages continue to affect later one.
To be fair I find these tools useful. They save time on repetitive changes, repository exploration, boilerplate, unfamiliar APIs, and many kinds of mechanical implementation work and some times compiling and using an unpopular libraries like FLTK and use them in a python project. They can also be useful for generating alternatives when I already understand the problem well enough to judge them apart.
The problem is keeping my own understanding of the project in line and keeping up with the amount of code and explanation being produced. That problem manifests in different ways. The following are my observations, and so are personal. Take it with a grain of salt.
When a session accumulates too much history
A feature often begins with a reasonably clear conversation. I describe the intended behavior, explain the relevant parts of the architecture, point the model at the files involved, and discuss an implementation. At that point the model and I are working with roughly the same picture of the system.
Then something fails. I inspect the error and make a change. That exposes another problem. A test fails. We examine another subsystem. An assumption that looked reasonable at the beginning turns out to be wrong. We alter the design. Some code written earlier in the same session is now obsolete.
After a few rounds of this, the conversation contains several different versions of the same feature. It may contain the original design, the first implementation, explanations of why that implementation failed, patches against files that have since changed, temporary debugging code, rejected approaches, old stack traces, revised assumptions, and the implementation that currently exists on disk.
The issue is not that the conversation is too long but it contains several historical states of the project at once, which may or may not be useful depending upon what you want to next. This is where I have repeatedly seen the quality of later edits deteriorate. This is not solved by keeping track of the evolution in a separate note, like changelog file or design document, as we will see later.
A proposed patch may make sense against a version of the file that existed earlier in the session but no longer exists in the repository. Sometimes this is obvious because the patch cannot be applied. Sometimes it is not obvious because the new code is plausible enough to fit into the current file while still depending on an outdated assumption.
Even the state of the art model suffer this when the context reaches 50k tokens and only get considerably worse from there.
A fresh session has the opposite weakness
At times you have to clear everything out and start afresh, and it usually improves things immediately. I can provide the current files and discuss only the problem that exists now. Failed experiments and old stack traces no longer occupy the working context. The cost is that the new session sees the current state without necessarily understanding how it came to exist. More importantly it is extremely hard at least for me to anticipate what things to extract from older sessions into a note or doc because.
- Side note
- I often see note taking application brag about automation features like summarizing blogs and videos for the user. Sure, they may have their place and there may be different types of notes based on personal taste and system (I will rant some other day). The most useful notes are the one that act as a index to what is actually residing in you mind. Without reading or watching a video, the utility of those auto generated notes are null.
An unnecessary abstraction may be there because a simpler version was already tried and failed. A strange piece of code may be compensating for a limitation discovered earlier.
I can explain all of this again, but then I become responsible for reconstructing the relevant project history every time I reset the conversation.
For small tasks that is acceptable. For projects with several subsystems and multiple implementation phases it becomes tiring. The information that has to be transferred is not only “what the code does now” but also “which earlier decisions still matter, which approaches were rejected, and why”.
So I end up with two unsatisfactory choices. Either, continue the existing session and preserve and accumulate the history, or start a new sessionto obtain cleaner context but reconstruct important history manually. A larger context window does not remove this problem. It can preserve more history, but preserving more history is not the same as representing current state clearly.
Subagents redistribute the same difficulty
Subagents can help with tasks that are genuinely separable. One agent can investigate a database problem, another can inspect a test failure, and another can work on an API change. The individual contexts remain smaller, which is useful.
The difficulty shows itself when the work overlaps. I have to know which version of the repository each agent saw, what assumptions each one made, which changes have already been incorporated, whether one agent has invalidated another agent’s conclusion, and whether two agents are solving the same problem in incompatible ways. The coordination burden is even more frustrating.
This matters because human attention does not scale in the same way that model instances do. It is hard enough fighting one model, now there is an army of them. A project may be decomposable into ten tasks, but I cannot necessarily supervise ten active lines of reasoning without losing track of their dependencies.
At that point it is tempting to introduce another coordination layer in tools like claude code, hermes: a planning agent, a shared specification, a phase tracker, a changelog, a summary file, or another mechanism for keeping the agents synchronized. In tools like aider, these can be managed via separate sessions, if one is patient enough. Each of those can be useful but also adds another representation of project state to remain accurate.
Documentation can become another synchronization problem
I use Org mode extensively, so my natural response was to write more things down. Trust markdown is not working either. For a substantial project I may have a specification, an implementation plan, architecture notes, phase notes, TODO items, a changelog, decision records, debugging notes, and summaries intended to help start the next session. None of these is pointless but not all projects require this and important problem is that those documents are interconnected and must remain interconnected to be of use in any meaningful sense.
If implementation reveals that an architectural assumption is wrong, the code changes first. Then the specification may need to change. The implementation plan may still refer to the previous architecture. The phase notes may contain conclusions based on an assumption that is no longer true. The changelog may need an entry. The next-session summary may need to explain the change. Everybody know programmers are not even commited to keeping the comments in-line with the code, can you expect them to maintain the documentation in sync. Before you ask, no it cannot be auto generated using the LLMs.
This is where spec-driven development can become awkward in practice. A specification written before implementation records what I believed before implementation. Implementation may teach me that part of the specification was wrong. If I continually rewrite the same specification, the current document becomes cleaner, but the sequence of reasoning that produced it disappears and history is often useful. Before the vibe-coding era, our mind dealt with all of these albeit approximately, but the gut knew what happened and why even though we were not able to articulate well.
When I return to a project after several weeks, it is useful to know the current design. It is also useful to know that an earlier design was tried and rejected because of a specific limitation. Without that information, both I and a new model session can easily repeat work that has already been done. So the notes have two conflicting purposes. They need to describe the current project clearly, and they need to preserve enough history to explain why the current project looks the way it does. Trying to make one continuously edited document serve both purposes has not worked particularly well for me. Even if someone could come up with the note-taking or documenting system, we will have to pay in tokens.
LLMs are also poor at maintaining large notes
There is a more mechanical version of the same problem. Large documents are difficult for models to edit reliably over time. A heading moves. I make a manual change. Another session edits a nearby section. A previous patch changes the surrounding text. The model later proposes another patch based on an older representation of the document. Eventually the patch no longer aligns cleanly with the file. The same happens with source code, but notes are especially vulnerable because they are often long, prose-heavy, and structurally repetitive. A patch can appear plausible while landing in the wrong conceptual location. The opposite approach is to regenerate the whole file. I have seen this with Aider and some model configurations, particularly with Kimi. A small change can result in a large file replacement rather than a narrow edit. That can be expensive in tokens, but the token bill is only one part of the cost. The larger problem is review. If a model changes ten lines, I can inspect ten lines carefully. If it rewrites two thousand lines to achieve the same result, I have to decide whether to trust that the other 1,990 lines remained semantically unchanged. diff tools helps, but I still have to look through all the hunks to make sure nothing was messed up. I cannot reliably offload this part. When you handwrite code, we know exactly what to note down reliably, but that is not the case with vibe-coding. This is one of the recurring patterns I have found with vibe coding: when a model-side limitation is solved by generating more material, the cost often resurfaces as human review work.
The amount of reading became difficult to justify
Over roughly five months I have read more than six million words of generated material related to programming. Six million after generously discounting 90 percent of sixty million accounting for token-word correspondence, thinking traces, and etc. Six million includes architecture discussions, implementation plans, debugging explanations, proposed patches, reviews, generated notes, test analyses, session summaries, corrections, and alternative designs. I do not consider that number an accomplishment. Much of the material was useful when I read it. The problem is how quickly its usefulness expired. I may spend twenty minutes reading an explanation of why a particular implementation is failing. An hour later we discover that the implementation was based on a mistaken assumption and remove it. The explanation did its job, but almost none of it remains useful after the design changes. This is normal to some extent. Programming has always involved temporary knowledge. Debugging requires reading logs and investigating possibilities that turn out to be irrelevant.
But a model can generate another explanation immediately. It can generate three alternatives, review each one, produce a migration plan, update a note, and then write tests for the result. All of this can happen faster than I can decide which parts deserve careful attention. The opportunity cost is difficult to ignore. If I had spent even half that amount of reading on books, papers, mathematics, programming language theory, history, literature, or other subjects that I care about, I would expect a much larger portion of the reading to remain useful to me years later. The comparison is not exact. Work and study serve different purposes. Still, the difference between cumulative knowledge and ephemeral reading has become noticeable enough that I no longer treat all time saved by code generation as an uncomplicated gain.
My focused working time became shorter
Before using these tools this heavily, I could spend long stretches of time on one programming problem. Sometimes I would work on the same problem for several days. That was not always healthy, but there was continuity in the work. I could keep the same representation, execution path, or architectural question in mind for a long time. Now I often find that one or two hours of serious agentic work is enough to exhaust the amount of generated material I can review properly. The model can continue. It can produce another patch, inspect another directory, propose another design, or rewrite another subsystem. I am the part of the system that has to stop. The amount of code I can cause to be produced in a day has increased considerably. The amount of code I can understand in a day has not increased at the same rate.
This is the point where the productivity discussion becomes less simple. If code production increases by a factor of ten but careful comprehension increases very little, then the extra output has to go somewhere. Either I slow the model down, postpone review, reduce the depth of review, or accept some changes without understanding them fully. The last option is easy to slip into because generated code is usually not obviously absurd. It is often plausible.
This is how I understand vibe coding now
I do not think vibe coding is restricted to people who cannot program. I and anyone can fall into it as well. The transition is usually ordinary. I carefully review several changes. I get tired from reading millions of words!. The next patch looks reasonable. The tests pass; application starts; explanation is convincing; I skim the change instead of reading it closely and approve it. Nothing dramatic, but some code has entered the project without going through the level of thought I would normally require before taking responsibility for it. Repeated often enough, this produces a repository that I don’t understand. I may know what a subsystem is supposed to do. I may be able to ask the model to explain how it works. That is not the same as understanding why the subsystem has its current structure, which assumptions it depends on, and where it is likely to fail.
For me, vibe coding is not simply “using AI to write code”. It is allowing accepted change to move faster than understanding. In rough terms rate of accepted change, is much greater than rate of comprehension. This is true for bot experienced programmer and a beginner if the volume of generated work is high enough.
Tests are not truly tests if the agent can rewrite them
Tests are often presented as the answer to this problem. Let the model write code, then let the test suite constrain it. I have become much less confident in that workflow when the same agent is allowed to modify both implementation and tests. LLMs often behave as though the immediate objective is to make the suite green. If a test fails, changing the implementation is one possible response. Changing the test is another. I have repeatedly seen agents alter tests to accommodate the implementation they already produced. The problem is more serious when the same model writes both implementation and tests. A misunderstanding of the requirement can be encoded in both.
Some implementation failures are really design failures
Another recurring difficulty is that a model can spend a long time repairing an implementation that should have been abandoned earlier. The model sees a local failure and tries to remove it, which is fine for most cases, but at times it may introduce completely different abstraction that don’t mesh with rest of the project. I have had features where several rounds of debugging eventually led back to an architectural assumption made near the beginning. Once that assumption was reconsidered, much of the generated code could be deleted. This is one reason I have become suspicious of treating cheap code generation as an uncomplicated advantage. Code may be cheap to produce, but it is not cheap to maintain. Every abstraction adds something that has to be understood. It has interfaces, state, failure modes, tests, configuration, and interactions with other parts of the program. Even if the model writes all of those things, I still have to maintain a mental model of them if I am going to take responsibility for the system. Generated complexity can therefore be unusually expensive. The cost of creating it is paid by the model. The cost of understanding it is paid by me.
Writing code is part of the thinking process
It is tempting to think that the important part is the design and that typing the implementation is merely transcription. My experience is that this is not how programming actually works. Writing an implementation forces decisions continuously. Depending upon which programming language you use, there can be different question, but the following are usually there in one form or another.
- Where does this state belong?
- Who owns it?
- What is allowed to mutate it?
- What does this function promise?
- What is the invariant here?
- What happens when this input is absent?
- Why does this abstraction exist?
- Can I remove it?
- Am I representing a real distinction in the problem, or only hiding an awkward implementation?
I do not answer all of these questions before I begin writing. Writing is one of the ways I discover that they need to be answered.Manual programming therefore kept construction and thought coupled more tightly, which is good.
Some programming friction is useful
Most software tools are designed to remove friction, and much of that is good. But not all programming friction is waste.
- Side note
- It may surprise you that I don’t use syntax highlighting at least not the way most programmers do ^1. I sometime use rainbow/prism based highlighting to visualize nesting. I rarely use advanced IDE features like LSP. I use xref albeit very little. This forces me keep the important bits of program in my head.
Writing a function manually can reveal that I cannot yet state what the function should do. Tracing an execution path can expose an architectural problem that disappears from view when I simply ask an agent to fix the error. Implementing an interface can force me to notice that two concepts I had treated as one are actually different. An agent can sometimes remove the immediate difficulty before I have understood why the difficulty existed. That is convenient, but convenience and understanding are not the same thing. There are situations where three hours spent thinking and writing twenty lines leaves me in a better position than three hours spent reviewing two thousand generated lines. The first produces less code but may produce a clearer model of the problem. I therefore want AI to remove mechanical effort without automatically removing the forms of friction that force useful questions.
I still intend to use these tools
None of this has made me want to stop using coding models. They remain useful for many tasks. I am comfortable delegating work when I understand the transformation being performed. Analysis of new repository, boilerplates and mechanical refactoring are good examples. Generating several candidate implementations can be useful when I want alternatives to compare. But I am more cautious and anxious these days when the model is changing the conceptual structure of the program. Architecture requires more attention (Architecture is such an inflated term to use for reasonably small programs anyway). Same goes for State ownership and Public interfaces, the tests that define whether the implementation is correct, and a large rewrite.
These are the places where I do not want the appearance of progress to substitute for understanding. Sometimes, I
- want the model to implement a decision I have already made.
- want it to challenge the decision.
- need to stop asking for implementation and think about the problem myself.
I do not want my role to become approval monkey
Obviously bad code is usually easy to reject. The more difficult case is plausible code. There is a mode of working where I gradually succumb and stop programming and only act as an approval monkey.
- The model proposes a change.
- The diff appears reasonable.
- I approve it and move on.
That can help me produce a large number of projects. But lets be honest it usually leaves me with a shallow understanding of the system. I do not want that to become my programming practice. I want to keep opening files and reading them. I want to write code when writing it is how I understand the problem. I want to question abstractions even after a model has already implemented them. I want to know why state lives in one place rather than another. I want to recognize when a subsystem should be deleted instead of repaired. Most of all, I want to be able to explain the important parts of my own project without first asking an AI to explain what it wrote.
All of them require keeping responsibility for the analytical part of programming.
I still want to be the programmer
AI can generate code much faster than I can, but how quickly I can allow a program to change while still understanding it. When those rates diverge too much, I need to slow down. Sometimes it means opening the source file and writing the code myself. The purpose of programming is not to maximize how much source code appears during a day. A program that runs but that I cannot reason about is not a satisfactory result merely because an agent can explain it back to me. I would rather maintain a smaller system whose structure I understand than supervise a larger one whose correctness I infer from tests and explanations produced by the same machinery that wrote it. I like AI in my programming environment but more than that I also want to be the programmer.
[1] https://www.linusakesson.net/programming/syntaxhighlighting/