System design when code is cheap
The price list of design decisions has inverted. Six principles, and a rule for how much to fix before any code is written.
The shortest way to hold this subject: models inverted the price list of design decisions. Implementation used to be expensive, so architecture served the efficient writing of code. Implementation is now cheap and regenerable, and three things have become expensive instead: verification, the attention of reviewers, and consistency across the whole. Good architecture in this era optimises for those three. And the codebase has a third user now, beside the developers and the runtime: the agents. The design has to be readable by all three.
Six principles follow, in order of importance.
1. Hard boundaries, enforced by machine
Architecture has to become a deterministic gate. This is the direct answer to the coherence gap: agents erode an architecture through a thousand small decisions no test catches. The fix is to turn architectural rules from culture (“we do not import from the domain into infrastructure”) into laws the machine checks: dependency rules with tools like dependency-cruiser, ArchUnit or import-linter, strict module boundaries, explicit public interfaces with the internals locked. An architectural rule that is not enforced in CI does not exist for an agent. Agents do not absorb culture from sitting near the team the way a junior does. This is the fitness function for architecture: one more deterministic gate in the set you already have, checking structure instead of behaviour.
2. Design for verifiability
Because verification is the bottleneck. Separate a pure logical core from a shell of side effects, so most behaviour can be tested without mocking half the world. Contract-first at every boundary: an API description, a protobuf definition, a schema with constraints, each a piece of intent the machine enforces permanently. Side effects gathered in one place rather than scattered. The question to ask of any design in review: how would an agent prove this part right? If the answer is “stand the whole system up and click through it”, the design is expensive at exactly the point where you are poorest.
3. Partition by blast radius
The conclusion of the previous piece, letting go of understanding by zone, is only possible if the zones exist in the architecture. The core domain, money, data, authentication, separated from the edge, with boundaries hard enough that a fault at the edge cannot reach the core. Then the risk tiers of the process map straight onto the directory tree: a hook blocks an agent’s edit to the core without sign-off, and the features directory is open. The architecture becomes the place where the autonomy policy is enforced, not merely a diagram of it.
4. Modules that fit one span of understanding
The right unit of design is the unit an agent, or a person, can load completely in one session: clear boundaries, documentation beside the code (a readme per module, a conventions file per level), names that match the business glossary. Naming is a form of specification. And the most important discipline at this level: one pattern per problem. Three ways to call an API in one codebase means an agent will pick one at random, or invent a fourth.
5. Prefer boring technology
Agents are at their best on the stacks that are dense in their training data: the common database, plain HTTP, the mainstream frameworks. An exotic technology or an in-house framework now carries a cost line it did not have before: every agent working on it is worse and guesses more. Boring technology was an aesthetic preference. It has become an economic decision.
6. Implementation is disposable, contracts endure
The deepest shift. When code regenerates cheaply, design so that the inside of a module can be thrown away and rewritten as long as the contract and the tests hold. This reverses an old instinct: the fear of duplication falls, because an agent fixes five copies about as cheaply as one, while the cost of a wrong abstraction is unchanged, so the balance tips further toward repeating yourself over abstracting prematurely. The durable assets are the interfaces, the invariants, the tests and the decision records. The code is the current rendering.
The process, and what to avoid
Two changes to how the design gets made are worth adopting at once. Use the model as a sparring partner: have it produce two or three architectures with trade-offs, then red-team the one you intend to choose, “list ten ways this fails at scale, in security, in operation”, and let a person decide, because system design is where judgement across the big picture concentrates, and that is the judgement no proxy replaces. And turn every settled decision into a short decision record, context, options weighed, choice, consequences, kept in the repository. A decision record now has two readers, the next person and every future agent, and it is the cheapest answer to the why that the comprehension gap will eventually ask.
Three anti-patterns. Letting the architecture emerge from agent pull requests, one micro-decision each, adding up to a locally valid mess. Splitting into microservices on the theory of one agent per service, when hard module boundaries inside one deployable give most of the benefit for a fraction of the operating cost. And over-engineering ahead of need: you-aren’t-gonna-need-it is more true than before, because the cost of adding something later has fallen so far.
How much to design up front
The question that follows is whether to design in detail, with the database, the API, sequence diagrams and data flows, or to stay at the outline and let the agent decide while generating. The principled answer is not to choose a level of detail by document type but by whether the thing is a contract or an implementation. A contract, which many parties depend on and which hurts to change, is designed first and in as much detail as you can afford. An implementation, which lives inside one boundary and is cheap to redo, is left to the agent at generation time, with documents generated back from the code if something has to be delivered. The line runs between one-way and two-way doors: decisions that are hard to reverse are fixed by a person first; decisions that reverse cheaply are given to the agent.
a person designs first, in detailtwo-way doors →
the agent decides, the document is generated after
The database design is the most detailed, and a person fixes it. Data outlives code: wrong code is regenerated, a wrong schema is a painful migration carrying real records. The schema is the implicit contract every module shares. And if each agent invents the schema for its own feature you get the coherence gap exactly: a naming style per feature, duplicate tables, contradictory relations. Fix first the entities, the relations, the constraints and the invariants, one order belongs to one tenant, what is unique, what cascades. Leave loose what is cheap to add later: performance indexes and auxiliary columns. A unique index is not one of those; it enforces an invariant and belongs in the list above. And “later” has a cost of its own on a large production table, where an ordinary index build blocks writes and the concurrent kind takes planning, so “cheap to add” means cheap to decide, not free to deploy.
The API design is detailed at the boundaries, above all the ones that face outward. A public API, an interface between large modules, an integration a customer builds against: write the full description first, because every consumer is locked to it. An internal interface inside one module can be left to the agent; it reverses.
Sequence diagrams are mostly derived documents, so do not draw them first. The exception is the handful of cross-boundary flows where order and failure modes are the design: payment, a distributed transaction, the sign-in flow. For an ordinary flow, a diagram drawn in advance is a description of code in pictures, doing the agent’s job for it and then having to be fixed in two places. Generate it from the code when the code exists.
Data-flow diagrams are drawn when there is a reason, not by default. Two reasons are legitimate: threat modelling, where sensitive data flows is a question that needs answering, and a document a customer, an auditor or a procurement process requires. Otherwise, derived.
There is a historical reading that makes this clearer. The detailed design document existed to coordinate people: the designer and the coder were different people, often in different companies, so the design had to be specified to the point where anyone holding it would produce the same code. Now the coder is an agent, and a per-task plan reviewed by a person is the detailed design, generated just in time, checked at the plan gate, then kept as a trace or thrown away. The layer of detailed design did not vanish. It moved in time, from written-before-any-code to generated-right-before-this-task. Risk tiering applies as always: a payment system gets its schema, its API and the sequence of every money flow in detail; an internal tool gets its main entities and one page, and the agent does the rest.
The pocket rule. Design first what many things depend on and what is hard to reverse: schema, contracts, invariants, the critical flows. Let the agent decide what lives inside one boundary and regenerates cheaply. Generate every descriptive document from the code instead of writing it ahead. If an artifact does not clearly fall in one box, ask: if this is wrong, when will I find out, and what will it cost to fix? Found late and expensive to fix means design first. Found early and cheap to fix means let the agent do it and check at the gate.
In Rindo a decision record is a page in the project with its own history, linked to the task that made the decision, where the next assistant can read it before touching the code it explains.