DEV Community

Cover image for How I enforce architecture on coding agents
Egor Kraev
Egor Kraev

Posted on

How I enforce architecture on coding agents

My previous post described my workflow for individual PR’s, and reasons why I believe it is better at delivering “locally good” PRs than I would have been writing by hand. However, that left open a big-picture gap: even if each individual PR is perfectly reasonable, how do I make sure the overall architecture doesn’t drift into spagetti gradually, by small increments?

That is, how do I make sure the code remains modular and well-organized, and expresses my intent, at a high level? That is what this post is about.

The short answer is simple: I spell out explicitly both the high-level structure of the code and the principles I expect it to embody, and then enforce both against the code. The first part is reasonably easy as far as it goes, and was being done as long as there is software - it’s called documentation. The second part is the hard bit, and the interesting one.

What does “good code” mean in the first place? Every one who has ever worked with developers knows this is an unexpectedly personal question - one person’s polished code is another person’s mess. My definition centers on two characteristics: firstly, it must be maintainable; and secondly, it must correctly express the intent behind the application/library. Both take some unwrapping.

For me, “maintainable” mostly means “modular” - composed of components with well defined and documented jobs and interfaces. As long as that’s the case, it’s reasonably easy to locate the component responsible for the bug or feature one cares about, and then one can turn the job over to coding agents who by now are quite good at doing smallish-scale optimization/problem solving. Of course, for that to work the components must map well to the overall purpose of the system - that’s the hard design part. The other part of “maintainable” is “thoroughly coverd by tests”, which again is easier in a modular design. The nice thing about this modular structure is that once in place, it’s fairly easy to enforce programmatically, even as new code is added to it.

The second part of what makes code “good” for me is the subtler one: does the code express the overall intent of the library/product? To some extent this is what spec-driven development tries to do, but that places the onus on the spec writer to make a lot of fine-grained choices to align each spec with the big picture. This is also the Achilles heel of good test coverage considered standalone: we may be testing everything, but who writes the tests, and how do we make sure each test is mandating the behavior that it actually should?

The solution to this for me is something I’d call principle-based development: explicitly spelling out, at different levels of the architecture, the principles that the software (or a particular component) must embody. These are not descriptive but normative: that is, their purpose is not to describe the behavior of current code, but to mandate what all relevant code, present and future, should fulfill.

Then each individual PR can consult these principles and make sure its plan aligns with them (and on a brownfield project, also fix any related violations it comes across near to the bits it touches).

This was the bit that was prohibitively tedious befor the LLM era - but now, it can just be a routine step in the planning and review stage of each PR. In fact, when the agent asks me to make a nontrivial design choice, I can just ask it to analyze which of the options aligns better with the relevant principles, and it will do so for me, with quotes and references.

In my next post, I will describe how I operationalized both of these in my development workflow for the SLayer repo, using Arc42, LikeC4 and some custom verification logic.

Top comments (0)