From building to architecting

From building to architecting

Software architecture is probably one of the most important skills for working on applications with coding agents. When an agent writes most of the code, the direction you give it becomes the architecture: which patterns to follow, where new code belongs, and how the pieces of the system fit together. A poorly architected application will be difficult for an agent to work in for the same reasons it's hard for a human to work in. So how do we do architecture in harness engineering, and how do we keep our applications high quality and easy to work on? The design patterns and principles we already know are still essential, plus some important new skills. Less of our time goes into building, and more goes into deciding how things should be built.

Software design patterns are still essential

Hopefully this is an obvious one, but the patterns that have made it possible for humans to build and maintain large applications are essential for agents too. A system with consistent patterns makes it easier to search codebases and find relevant parts of an application, regardless of who is doing the searching. Understanding where a change fits in with its call stack is essential too - it makes it so that agents can write or change a single function without having to verify that there aren't surprises lurking elsewhere in the project.

When building software with coding agents, the difference is that you should be explicit about the designs and patterns you want your agent to use. You can tell your agent what matters most to your application - speed of delivery, application performance, or any other metrics - and have it help you curate the many different principles and patterns that exist into a set that makes the most sense for your application.

SOLID

The SOLID principles (Single responsibility, Open-Closed, Liskov substitution, Interface segregation, and Dependency inversion - see Wikipedia if you are unfamiliar) aren't really design patterns, but they can guide a lot of decisions and improve things within design patterns. For example, the single responsibility principle can be applied to an observer (from the Gang of Four) or a factory.

Agents are very familiar with and good at the SOLID principles. When I'm giving instruction to an agent to improve an area of an application I may tell them to consider improvements by applying these principles. I haven't had much luck putting them into the AGENTS.md file directly as it often leads agents to over adhere to them. This can result in things like very small interfaces to prevent coupling code together when not necessary, which can make the code very difficult to understand.

Principles in AGENTS.md get over applied

When I put SOLID directly into AGENTS.md, my agents treated them as absolute: ridiculously small interfaces and convoluted abstractions. Principles work better inside specific skills, like planning or a quality review pass.

Another useful time to point to a specific principle is when creating agent skills. For example, if you have a clean up skill (or quality review skill) you might want it to watch for the single responsibility principle to make sure that the responsibility of a module or class hasn't drifted away from its original intent. Or you may want to have the open-closed principle be part of a planning skill. The dependency inversion principle can be another good one to include in a quality or "finishing work" skill.

Gang of Four

Being able to identify and properly apply patterns is critical. Without clear direction on patterns your coding agent will either pick one (possibly at random) or (worst case) will just dump all of your code into one file or function (this has happened to me before). You should spend some time getting familiar with the patterns proposed in the popular Gang of Four book, Design Patterns: Elements of Reusable Object-Oriented Software, at least at a high level. You can work with your coding agent to learn these patterns. It can also give you a quick overview of when to use each one.

Once you have a decent familiarity with the subject, coordinate with your agent to figure out which patterns make the most sense for your application. Ask it questions and have it ask you questions too, so you can be confident that you have selected the patterns that align with the expectations you have for your application.

If you're working on an existing legacy application, ask your agent what code patterns are in use. More than likely there will be several, each followed to a different degree, so you might also want to spend some time taking notes (or having the agent take notes) about what parts of the application require attention. You can use these notes to help guide future technical debt improvement or give agents specific instructions on parts of the code that are high risk.

One last thing: knowing the Gang of Four patterns can help you do code review faster. You can look for specific indicators (like method signatures, interfaces, or other "top level" structures) to see what is changing in the application without looking at every line of code. It's a good way to spend less time reading code that doesn't matter for understanding how the application works.

Just a starting point

SOLID and the Gang of Four are where I'd start if you're new to design patterns, but they're far from the only options, and they aren't necessarily the best ones for your application. Martin Fowler's Patterns of Enterprise Application Architecture covers patterns like Repository and Unit of Work for data-heavy applications, Domain-Driven Design has its own vocabulary for modeling complex business logic, and most languages and frameworks come with idioms of their own.

What matters is that you pick a set of patterns on purpose, based on what your application needs, and that both you and your agent know what they are. Your agent can help here too: ask it which patterns are common for your stack and what tradeoffs each one makes.

Plus some new things

Our job as engineers now is to figure out how to adapt these design patterns and coding principles for agents. We need to learn how to properly specify the design patterns for an application, tell agents how they should be applied, and tell agents when they should be ignored. Without our guidance agents may over engineer (add complexity when it is not necessary) the application, which will inevitably result in agents working slower (spending extra tokens to do the same task) or introducing bugs.

These new skills fall into two groups: feed forward (the documentation and skills that tell the agent what to do before it starts) and feedback (the linting, validation, and CI checks that tell it when it got something wrong).

Use agents to specify design patterns

As covered in the Gang of Four section, have your agent help you define the best patterns for your goals in the code. Then have it write these down into the AGENTS.md or other documentation files, which is your feed forward. Have it build linting rules or validation scripts that flag other design patterns and bring them to your attention, which is your feedback (more on that below).

Here's an example of what a pattern entry in AGENTS.md might look like:

## Third-party integrations
- Wrap every third-party API client in an adapter in `src/integrations/`,
  one adapter per service.
- Application code calls the adapter's interface instead of the vendor SDK.
- Exception: prototypes in `src/spikes/` can call vendor SDKs directly while
  you evaluate them.

Say what to do, not what to avoid

Instead of "avoid the bridge pattern," try "use the adapter in src/integrations/ when wrapping third-party APIs." Agents don't do a good job of distinguishing negative and positive - even mentioning a pattern, to say don't use it, can make an agent more likely to use it.

Distinguish between goals and reality

As mentioned in the Gang of Four section, legacy projects will inevitably have various patterns. Without specific instructions, agents will often just make new code look like the code near it, which may not be the pattern you want to keep. Encourage your agents to be consistent with the patterns you've chosen, but also ask them to bring old code into new patterns as work is done on those files. There is a balance here, so be careful with the way you word your instructions. Otherwise you may end up with every PR being a massive rewrite of some part of the application.

Agents copy their neighbors

In a legacy project, the code an agent reads first can become the template for everything it writes. If the nearest file is your oldest and messiest one it may copy that pattern, spreading disaster. Point the agent at a good example instead, like a file or PR that shows the pattern you want.

Design patterns are not absolute

The existing design patterns and principles are still essential, but agents treat them differently than humans do. When you give an agent instructions they are designed to follow them. But humans understand that these instructions are not absolute, that they have the ability to pick and choose when they are appropriate, or when they are causing the code to get overly complex. The engineer is responsible for finding the balance on when and how to instruct agents to use design patterns and principles, and when to tell the agent to "back off" in order to keep code from getting convoluted.

Validation and tooling

Agents also have the opposite problem. They follow instructions too closely when the instructions are right in front of them, but they lose track of them as a task goes on and their context fills up. Careful wording fixes over adherence, and feedback from tooling fixes drift. If you want your agents to strictly follow the design patterns you lay out, it is important to provide scripts and tools to verify that patterns are followed. I would recommend having any validation or verification scripts run in CI so the agent can't forget to use them.

Here's a lint rule that enforces the adapter pattern from earlier, keeping application code from calling a vendor SDK directly:

// eslint.config.js
export default [
  {
    files: ["src/**"],
    ignores: ["src/integrations/**", "src/spikes/**"],
    rules: {
      "no-restricted-imports": ["error", {
        paths: [{
          name: "stripe",
          message: "Call Stripe through src/integrations/stripe. See AGENTS.md > Third-party integrations.",
        }],
      }],
    },
  },
];

Pay attention to the message. When the rule fails, the error points the agent back to the relevant section of AGENTS.md, so the feedback becomes feed forward for its next attempt.

Conclusions

As software engineers, it's now our job to decide how the application is structured. We used to be able to tune the code toward the patterns that fit the application gradually, one change at a time. With agents, that work has to happen earlier and more explicitly. We define the patterns up front and explain when to use them (and when not to). We back them up with linting, validation scripts, and CI gates so they're actually followed. And we watch what our agents build, adjusting our guidance when they drift or start over engineering.

We move from being a builder to being an architect: spending less time writing the code ourselves and more time deciding how it should be written, then making sure it stays that way.