Building Better Software Together
Coding agents have changed how software gets written. They can produce more code, faster, than any team could a few years ago. But more code is not the same as better software. What turns that extra speed into something useful is the same thing that always has: good engineering practices and a clear architecture.
My approach rests on two pillars. Modern engineering practices help people and agents build, deliver and improve software. Strong architectural foundations give them clarity, context and boundaries. Together, they create systems that are easier for people to understand, easier for agents to work within, and easier to evolve over time.
This page explains why each pillar matters, then goes through the practices and foundations I rely on and why each one helps when people and coding agents build software together.
Modern Engineering Practices
How people and agents build, ship and learn.
When code was expensive to write, the cost of writing it hid a lot of weak process. A slow pipeline or a manual release didn't matter much if a change took a week to build. Agents have removed that cover. Code is now cheap and fast to produce, so everything around it, such as testing, review, release and learning from production, is where teams speed up or slow down.
Modern engineering practices are how a team turns a stream of changes into working software in front of users. They give fast, automated feedback, so mistakes are found in minutes, not weeks. They make small, safe changes the normal way of working. And they give everyone, people and agents, the same signals about whether a change is good.
Those shared signals matter more than they used to. An agent can't tap a colleague on the shoulder to ask if something looks right. It relies on the feedback the system gives it: tests that pass or fail, builds that break, checks that flag a problem. The better those signals are, the more an agent can do on its own, and the less a person has to catch by reading every line.
| Practice | What it is | What it gives people | What it gives agents |
|---|---|---|---|
| AI-Assisted Engineering | AI tools in everyday engineering work | More time for design and judgement | Clear guidance and shared context to work from |
| Agentic Workflows | Agents carrying out tasks with several steps | Real work done in parallel, with checkpoints | A clear task, the right tools and set limits |
| Cloud-Native Engineering | Repeatable environments defined in code | Cheap, safe experiments | Infrastructure they can read and change |
| Developer Experience | How easy it is to do good work | Quick setup and less frustration | Clear conventions and fast feedback |
| Continuous Delivery | Small changes, releasable at any time | Smaller, safer releases | Small batches that are easy to check and roll back |
| Observability | Seeing what production is doing | Problems found and understood quickly | Telemetry to find causes, not guess them |
AI-Assisted Engineering
Using AI tools in day-to-day engineering work: writing code, tests and documentation, exploring unfamiliar code, and reviewing changes.
Why it matters: AI-Assisted Engineering
Used well, AI-assisted engineering takes away a lot of the slow, repetitive work and leaves people more time for design, judgement and the problems that need real thought. Used badly, it produces more code than anyone can review and moves the work from writing to correcting. The difference is in how a team adopts it: clear guidance on when to use it, shared context the tools can rely on, and an expectation that a person still owns and understands every change that's merged.
Agentic Workflows
Giving agents tasks with more than one step, such as planning, making changes, running checks and fixing what fails, rather than just completing code in an editor.
Why it matters: Agentic Workflows
This is where the biggest gains and the biggest risks are. An agent that can run the tests and fix its own mistakes can finish real work while a person focuses on something else. But it can also go a long way in the wrong direction. Good agentic workflows define the task clearly up front, give the agent the context and tools it needs, set clear limits on what it can change, and include checkpoints where a person reviews intent and outcome, not just the diff.
Cloud-Native Engineering
Building software to run on cloud platforms from the start: managed services, infrastructure as code, automated environments and services designed to scale and recover.
Why it matters: Cloud-Native Engineering
Cloud-native engineering makes environments repeatable and disposable. When infrastructure is defined in code, an agent can read it, change it and have those changes reviewed like any other code. Teams can spin up an environment to try something and throw it away afterwards. That makes it cheaper and safer to experiment, which matters when agents can produce more experiments than ever.
Developer Experience
How easy it is for people to do good work on a codebase: how quickly they can set up, build, test, find their way around and get changes shipped.
Why it matters: Developer Experience
Developer experience used to be about people only. Now it applies to agents as well. Most of what makes a codebase pleasant for a new engineer, such as a quick setup, clear documentation, consistent conventions and fast feedback, is exactly what makes it work well for an agent. Investing in developer experience pays off twice: people are more productive and less frustrated, and agents produce better results with less correction.
Continuous Delivery
Keeping software in a state where it can be released at any time, with automated build, test and deployment pipelines and small, frequent changes.
Why it matters: Continuous Delivery
Continuous delivery is the safety net for working at speed. Small changes are easier to review, easier to test and easier to roll back. When agents increase the number of changes a team makes, the ability to release small batches safely and often stops the work piling up into large, risky releases. It also shortens the time between making a change and learning whether it was the right one.
Observability
Being able to understand what a system is doing in production from the data it produces: logs, metrics and traces that answer questions you didn't know you would need to ask.
Why it matters: Observability
The real test of any change is how it behaves in production. Observability turns production into a feedback loop: every change goes out, the system shows how it behaves, and what the team learns shapes the next change. It lets a team see quickly when something goes wrong, understand why, and learn which changes actually made a difference.
That loop works for agents too. Tests tell an agent whether a change does what was asked. Telemetry tells it what happened once real users and real data were involved. An agent that can read logs, metrics and traces can help find the cause of a problem and suggest the fix, rather than guessing from the code alone. As more code is written by agents, and fewer people have read every line of it, being able to see what the system is really doing becomes even more important.
Strong Architectural Foundations
How software stays understandable and adaptable, for people and agents.
Architecture is the set of decisions that shape a system: how it's split up, where its boundaries are, how the parts talk to each other and which rules they follow. Good architecture makes the right change easy to find and the wrong change hard to make. Poor architecture slowly turns every change into a risk, until the system becomes a big ball of mud that nobody wants to touch.
Agents make this more important, not less. An agent works from the context it's given. In a system with clear boundaries, consistent patterns and names that match the business, an agent can find the right place for a change and follow the patterns already there. In a tangled system, it copies the tangle and spreads it faster than any person could. Architecture is how a team gives agents the context they need and sets the boundaries that keep their work contained.
It's also how people stay in control. As agents write more of the code, the architecture is what people still need to understand and own. If the structure is clear, a person can reason about a change without reading every line. I've written more about this in Why Architectural Thinking Matters More Than Ever.
| Foundation | What it is | What it gives people | What it gives agents |
|---|---|---|---|
| Software Design Principles | Clear responsibilities, loose coupling, simple code | Code that stays easy to change | A standard to follow, written into their guidance |
| Domain-Driven Design | Software shaped around the business | A shared language with the business | Business terms that map to the code |
| API-First Design | Contracts agreed before code | Teams that can work in parallel safely | A precise target to build against |
| Vertical Slice Architecture | Code organised by feature | Small changes in one place | A small, self-contained area to work in |
| Modular Monoliths | One application, well-separated modules | Clear ownership without distributed-system costs | A clear limit on what a change should touch |
| Evolutionary Architecture | Designed to change, with automated checks | Change in small, safe steps | Drift caught automatically |
Software Design Principles
The long-standing principles of good design, such as clear responsibilities, loose coupling, high cohesion and simple, readable code.
Why it matters: Software Design Principles
These principles have lasted because they make code easier to understand and change. Agents don't make them obsolete. They make them easier to apply and easier to ignore. An agent can produce working code that breaks every one of them. Teams that know these principles can recognise the difference, describe it in the guidance they give agents, and keep the codebase in a state where every new change is as easy as the last.
Domain-Driven Design
Designing software around the business domain: using the same language as the business, and splitting the system into bounded contexts that each have a clear purpose.
Why it matters: Domain-Driven Design
Domain-Driven Design gives a system names and boundaries that mean something. When the code uses the same words as the business, people outside engineering can follow the conversation, and agents can connect a request written in business terms to the code that needs to change. Bounded contexts keep each part of the system focused, so a change in one area is less likely to leak into another.
API-First Design
Designing the contract between parts of a system, or between a system and its users, before building the implementation.
Why it matters: API-First Design
An API is a promise about how something behaves. Designing it first makes that promise explicit and agreed before anyone writes code. That gives people and agents a clear target to build against, lets teams work in parallel, and makes it obvious when a change would break someone else. For agents in particular, a well-defined contract is precise context that leaves little room for guessing.
Vertical Slice Architecture
Organising code by feature rather than by technical layer, so everything needed for one use case lives together.
Why it matters: Vertical Slice Architecture
Vertical slices keep related code close together. To understand or change a feature, you look in one place instead of following it through controllers, services and repositories across the codebase. That makes changes smaller and easier to review, and it gives an agent a small, self-contained area to work in, with less chance of affecting unrelated features. I've written about trying to measure this in Can We Measure The Impact Of Vertical Slice + DDD?.
Modular Monoliths
A single deployable application made up of well-separated modules, each with its own boundaries, rather than one tangled codebase or many small services.
Why it matters: Modular Monoliths
A modular monolith gives many of the benefits of microservices, such as clear boundaries and independent areas of ownership, without the cost of running a distributed system. For most teams it's a simpler, safer starting point. The module boundaries give people and agents a clear map of the system and a clear limit on what a change should touch, and a module can still be split out into its own service later if there's a good reason.
Evolutionary Architecture
Designing a system to change over time, making decisions as late as is sensible and using automated checks to keep the architecture on track as it grows.
Why it matters: Evolutionary Architecture
No architecture is right forever. Needs change, teams learn and technology moves on. Evolutionary architecture accepts this and makes change part of the design. Automated checks, sometimes called fitness functions, can test that the rules of the architecture still hold, such as which modules may depend on which. Those checks are especially useful with agents, because they catch architectural drift automatically, long before a person would notice it in review.
Bringing Them Together
How the two pillars work as one.
Each pillar is weaker on its own. Practices without architecture deliver changes quickly into a system that gets harder to change. Architecture without practices gives a clear design that's slow and risky to ship. The value comes from how they support each other.
| Weak architecture | Strong architecture | |
|---|---|---|
| Weak practices | Slow to ship and hard to change | A clear design that's slow and risky to ship |
| Strong practices | Fast changes into a system that gets harder to change | Better software, built together |
Each Pillar Makes The Other Work
Good architecture makes good practices easier. A vertical slice or a well-bounded module leads to small changes, and small changes are what continuous delivery depends on. Clear boundaries make tests easier to write and failures easier to find. Names that match the business make logs and traces easier to read when something goes wrong in production.
Good practices keep the architecture healthy. Automated checks in the pipeline catch a broken boundary as soon as it appears, not months later. Observability shows where the design is under strain. Fast, frequent releases mean the architecture can evolve in small, safe steps, rather than in a risky rewrite every few years.
Context And Feedback For Agents
For coding agents, the two pillars give the two things they need most. Architecture gives them context: where a change belongs, which patterns to follow and which boundaries not to cross. Practices give them feedback: tests, builds and checks that tell them quickly whether a change is right, and telemetry from production that shows how it actually behaves.
With both in place, an agent can take on more of the work with less supervision, because the system itself guides it and catches its mistakes. Without them, every change depends on a person reading the diff closely, and that doesn't scale when agents can produce more changes than any team can review.
Judgement And Ownership For People
None of this takes people out of the loop. It changes where their time goes. Less of it goes on writing every line and checking every diff. More of it goes on the decisions that shape the system: what to build and what not to build, where the boundaries should be, what good looks like, and whether the result actually solved the problem.
That's the work agents can't do for us. I've written about one part of it, deciding how much should go into the system at all, in If Review Is The Bottleneck, Maybe The Bottle's Too Full.
Better Software, Together
In the end, it comes down to people and agents each doing what they do best, in a system designed for both. Modern engineering practices keep the work moving and the feedback flowing. Strong architectural foundations keep the system clear and the work contained. Together, they give people and agents fast feedback, clear context and safe boundaries, which is what it takes to build better software together.