
Coding Agents, Structural Conservatism, and Why Domain Changes Need Refactoring
One pattern I keep noticing with coding agents is that they tend to evolve codebases in an append-oriented way.
When asked to implement a new capability, an agent will often find the closest existing class, service, module, package, or abstraction and extend it. It adds another method, condition, property, dependency, or interface implementation. In many cases, the result is technically correct. The feature works, the tests pass, and the diff remains relatively small.
But there is a deeper architectural question that often goes unasked: does the existing structure still represent the domain correctly after the change?
Software systems do not only accumulate behavior. Their underlying concepts evolve as well. New business rules can change the meaning of existing entities. Responsibilities that once belonged together may need to be separated. A concept that was once useful may become too broad, too ambiguous, or obsolete.
Coding agents often appear structurally conservative in these situations. They tend to preserve the existing model of the codebase and modify its contents instead of questioning whether that model still fits the domain.
Why Structural Conservatism Happens
That behavior is understandable. Small changes are safer. They reduce the risk of breaking public APIs, dependencies, tests, integrations, module references, and downstream assumptions.
Renaming a module, moving a class between packages, splitting a service, or replacing an established abstraction creates a much larger blast radius. From the perspective of completing a task successfully, extending the current structure is often the least risky option.
Humans do this too. Under deadline pressure, adding another fifty lines to an existing application service often feels more practical than reconsidering whether that service should still exist in its current form.
Coding agents can amplify this tendency because they are usually asked to implement a feature, not to reconsider the domain model in light of that feature.
When the Domain Changes but the Abstraction Does Not
Consider a system that originally has a UserService.
At first, that may be a perfectly reasonable abstraction. Over time, however, the domain may evolve until "user" no longer represents one meaningful concept. The system may now distinguish between a customer, a member, an account holder, and an identity.
An append-oriented implementation may keep extending the same service:
UserService createCustomer() verifyIdentity() updateMembership() updateAccountPreferences()
Each method may work correctly on its own, but the abstraction itself becomes increasingly misleading. At that point, the problem is no longer missing behavior. The problem is that the structure of the code no longer reflects the structure of the domain. A better response may be to separate those responsibilities into clearer domain and application boundaries:
Identity Customers Membership Accounts
That may involve introducing new modules, moving classes between packages, renaming existing types, splitting application services, changing dependency boundaries, or removing the original UserService entirely. The right change may therefore require structural work, not just behavioral work.
Behavioral Change Is Not the Same as Conceptual Change
This highlights an important distinction between different levels of software change.
At the simplest level, there is syntax: where should this code go?
Then there is behavior: what needs to happen for the feature to work?
But there is also a deeper question: what does this feature tell us about what the system actually is now?
Coding agents are becoming very good at the first two questions. The third is much harder.
That third question is where architectural evolution happens. It is where a developer recognizes that an existing class name is no longer accurate, that a module now contains several distinct concepts, or that responsibilities that once belonged in the same package should now be separated. It is also where deleting an abstraction can be more correct than extending it.
Existing Structure Is Often History, Not Truth
One reason this problem persists is that existing code structures are often treated as facts when they are really historical artifacts.
If a codebase contains an OrderService, an agent is likely to assume that OrderService is a valid and enduring concept. A developer with enough domain context may look at the same service and conclude that there is no meaningful OrderService anymore.
What actually exists today may be pricing, checkout, fulfillment, returns, inventory allocation, and payment orchestration. The service survived because the system evolved gradually. The architecture did not necessarily evolve with it. The same thing can happen at a larger scale. A codebase may still contain a module such as:
Orders
even though the responsibilities inside it now belong to several distinct business capabilities. The module name then stops describing the domain and starts describing the history of the codebase.
This is why I think "structural conservatism" is a useful way to describe the behavior. The agent tends to preserve the existing shape of the system unless there is a strong reason not to. Existing classes, modules, packages, and dependency boundaries become the default assumptions for future work.
A Correct Patch Can Still Make the Architecture Worse
There is also an incentive problem. Coding agents are usually evaluated on whether they can produce a correct patch. Does the feature work? Do the tests pass? Does the application build? Did the agent avoid unnecessary changes?
Those are useful criteria, but they do not tell us whether the repository became a better representation of the domain. A patch can be completely correct from a behavioral perspective while making the architecture slightly worse.
Repeat that pattern across dozens or hundreds of changes, and the effect compounds. Application services keep growing. Interfaces accumulate unrelated responsibilities. Compatibility layers become permanent. Modules stop matching the concepts they contain. Packages become containers for historical functionality rather than meaningful boundaries.
New domain entities are forced into old abstractions simply because those abstractions already exist. The software still works, but understanding it becomes harder.
Domain Changes Should Trigger Refactoring
This leads to an important practical implication for teams that use coding agents heavily. If a change introduces a new domain concept, changes the meaning of an existing entity, alters ownership or responsibility, or invalidates a previous boundary, implementing the feature should not necessarily be considered the end of the work.
There should often be an explicit refactoring step after, or as part of, that change. The purpose of that refactoring is not cleanup for its own sake. It is to bring the structure of the codebase back into alignment with the structure of the domain.
That may mean renaming classes and interfaces so that the language of the code matches the language of the business. It may mean moving types into more appropriate modules or packages, splitting an application service, introducing a new boundary, changing dependencies, separating domain models, or removing abstractions that no longer represent anything meaningful. Sometimes it may even mean reorganizing the application structure itself.
For example:
Before
Users
UserService
User
UserRepository
After
Identity
Customers
Membership
Accounts
The exact structure will depend on the domain. The point is not that every new concept deserves a new module or package. The point is that conceptual changes should trigger a structural question:
Does the architecture still tell the truth about the system?
Refactoring Keeps the Code Aligned With the Domain
This matters because if the domain changes but the architecture does not, the codebase gradually begins to encode two different realities: the one the business operates in today and the one the software was originally designed around.
Coding agents can make that gap grow faster because they are very effective at adding behavior to existing structures. So when domain entities, concepts, responsibilities, or boundaries change, refactoring should become a deliberate part of the workflow. The feature may be complete from a behavioral perspective, but the architectural work may not be.
Refactoring in this context is not just about making code cleaner. It is about preserving the relationship between the software model and the domain model.
From Patch Generation to Software Design
I think one sign of a more capable coding agent will be its ability to recognize when preserving the current structure is actually the riskier choice.
A strong agent should sometimes be able to conclude that a requested feature changes the conceptual model of the system, and that the implementation should therefore include changes to class names, modules, package boundaries, ownership, dependencies, and application structure.
That is a very different capability from simply generating a correct patch. It is the difference between adapting code to new behavior and adapting the software model to a new reality.
As coding agents take on a larger share of day-to-day implementation work, that distinction will matter more and more. The challenge is not only to make agents better at writing code or producing correct changes. It is to move them beyond append-oriented change toward concept-aware structural change.
That is where coding agents start to behave less like patch generators and more like software designers.
Leave a Reply
Your e-mail address will not be published. Required fields are marked *