I miss coding.
I never decided to stop writing code by hand. I kept giving the agent slightly larger pieces because it kept working. My editor is still always open, but I spend more time in Lazygit reviewing changes than writing code. This is after sixteen years of configuring Neovim to become an IDE.
I ship more now. I build things I would not have found the time or patience for before, and I can discard an idea without first spending two days implementing it. I am not going back to how I worked before 2023.
“I have unlimited tokens” is a wild flex , and I am happy for him.
For others, making a living by pressing Enter sounds incredibly depressing.
I understand both reactions.
Armin Ronacher calls some of this involution : more effort and more output without much improvement in what each person gets done. His agent factory ran for 35 hours and produced 75,000 lines of code and 79 commits. None of it was worth keeping.
My users still expect working software. So this is how I work now.
My contract with the agent
My arrangement with coding agents is that I own the interfaces and the agent deals with much of the implementation.
I decide what goes in, what comes out, which failures the caller sees, and what the interface promises to keep stable. Once other code depends on that shape, changing it gets expensive. I care much less about exactly how a local loop is written, provided it is clear, tested, and stays behind the interface.
This works well enough that I do not automatically reach for the latest frontier model. Everyone who uses these models for serious work knows that coding models peaked at Opus 4.6.1 Later models are more inclined to push back or relitigate a decision I have already made. I suspect some of this comes from training against sycophancy. Fair enough, but contrarianism is just as annoying when I am trying to get something done a particular way.
It is also why I worry when people say they have surrendered and no longer look at the code. Stop checking and the model’s preferences become the design.
The contract is easy to state and difficult to specify. “Make the code modular” is nearly useless. The agent may produce a hundred small modules, each exposing almost as much complexity as it contains. Following one operation means opening nine files.
I usually want deep modules: a small interface hiding a useful amount of functionality.2 Callers should not need to know how the work is arranged inside, and changing it should not require a tour of the repository.
Of course, a clean interface can hide a giant loop and 300 conditional statements. It works. I still do not want to touch it. “Implementation detail” cannot mean “code I never need to understand.”
This is not about taste, or me being picky, or believing I am smarter than the model. These are simply the heuristics I use for code I do not mind maintaining.
Five sessions away
Writing code was part of how I learned what I was building. An awkward call site told me when an interface was wrong. A parameter passing through four layers usually meant something lived in the wrong place. I encountered those problems while the design was still forming.
Writing it myself did not guarantee good design. It did limit how quickly unfamiliar decisions entered the repository.
An agent can return an implementation, tests, and documentation before I have formed an opinion about its first design choice. While reviewing it, I notice another possibility and start a second session. Soon several plausible changes are moving at once.
The result is the constant anxiety that I am five or six sessions away from breaking everything. I have not reached the point of being done with this way of working , but I understand how someone gets there.
Usually nothing is broken. The tests pass, the code compiles, and every change has a reasonable explanation. I am anxious because my understanding of the system may no longer be keeping pace with the system itself.
Reviewing everything carefully is the obvious answer. I do not want to review generated code full time. The day becomes a queue of small decisions: keep this abstraction, reject that dependency, ask for another test, collapse these modules, split this function, check whether the library call exists, work out whether the agent quietly widened the task.
No single decision is especially hard. The accumulation is exhausting.
Where I slow down
I use a small review skill in Python projects, and recently wrote a repository-local Go version for sbox. Neither tells me whether code is good. They tell me where to stop and read.
The first check is CRAP1, short for Change Risk Anti-Patterns.3 It combines cyclomatic complexity with test coverage. Branchy code with weak tests rises to the top.
Cyclomatic complexity starts at one and increases with each route through a function:
The formula is crude, especially when line coverage stands in for path coverage. I am not interested in the score as a grade. I want to know which function is most likely to ruin my afternoon when it changes.
My first Python version treated missing coverage as zero and called the score an upper bound. The Go version reports coverage as unknown and refuses to calculate CRAP1. A tool looking for false confidence should avoid manufacturing some of its own.
The second check comes from Rich Hickey’s use of complecting: braiding things together.4 This is not the same as counting branches. A function can be easy to follow and still mix policy, mutation, network calls, and persistence so that they become difficult to change separately. Code with several branches may still do one coherent job.
The sbox tool looks for control logic crossing multiple effect boundaries, and for functions that mutate external state while returning a value. An installer is supposed to coordinate processes and output. A finding is a reason to look at what else it has picked up along the way.
The third check borrows a few suspicions from Rob Pike: distrust clever algorithms before measuring, remember that n is often small, use the standard library when it already does the job, and look at the data before reaching for a more elaborate algorithm.5
These checks are deliberately imperfect. A nested loop may be right. Six parameters may really be six independent values. The tool reports its confidence and the evidence it used, then leaves me to read the code.
The comment at the top of the Go implementation says it “emits evidence rather than pretending heuristic findings are facts.” On its last run without coverage, it inspected 44 functions and pointed at three. That is useful. I can read three functions.
It does not catch a hallucinated API, prove security, or tell me whether the feature should exist. It runs after the ordinary correctness checks, when I am deciding whether I am prepared to live with the implementation.
The approval layer
The obvious next move is another agent. One writes the code, another inspects the architecture, a third checks the tests, and perhaps a chief of staff coordinates them. Before long you have a very impressive graph of agents reviewing agents, and even more work to verify. Please make it stop.
Some things matter more than whether I will enjoy maintaining the code. At work I have built one harness that works through SAST and Dependabot findings, dismisses false positives, and opens an issue when a security decision is missing. Another checks that changes to business behaviour, such as refund rules, trace back to an approved requirement. Both beat threat modelling and compliance in spreadsheets.
They are useful, but they still leave decisions for me. I might write about those harnesses in a future post.
I have no benchmark or science to support this. It is just my humble, extensively field-tested opinion. ↩︎
John Ousterhout develops the distinction between deep and shallow modules in A Philosophy of Software Design . ↩︎
Alberto Savoia and Bob Evans introduced the CRAP metric as a way to combine complexity and test coverage into an estimate of change risk. The implementation discussed here uses line or statement coverage as a proxy rather than true basis-path coverage. ↩︎
Rich Hickey, “Simple Made Easy” , Strange Loop 2011. ↩︎
Rob Pike’s “Notes on Programming in C” includes the five rules often summarised as measuring before tuning, keeping algorithms simple, and paying close attention to data structures. ↩︎