The Spec Can Come Later

When I say things like "delete the code, keep the spec," many people seem to interpret that as a rule: write the specification first, and only then are you allowed to program. That's not what I mean. It's also not how anyone actually builds software. I think this is the source of a lot of pushback I've heard not just to my own ideas but to any of the various spec-driven development approaches that have spawned in the past couple of years.\ Experienced developers know this in their bones. Programming is one of the most important ways we find out what we actually want. You try something, it's wrong, and you change your mind. Some requirements don't exist until an implementation breaks them. Here's a rule I know to be true: My first attempts at specifiying a system are always wrong. This is a fact I've treated as a rule since I first started programming in earnest in the 90s. I think this is what drew me to eXtreme Programming immediately when I saw it. "A user story is the promise of a conversation". Sometimes it's a conversation with the business owner. Sometimes it's a conversation with unit tests and a compiler. Matteo Paltenghi and Satish Chandra seem to have had the same intuition. AfterVibe: What Remains When the Conversation Ends explores this scientifically. They extract specifications from finished AI coding sessions and the code changes those sessions produced. Then they hand the recovered spec to another agent and ask it to rebuild the same changes in the existing repository. That's the part I find exciting. The first implementation is how you discover what you mean. The second is how you find out whether you wrote it down. So the order flips. You start with a vague idea and hack until something works. You come out the other side with a spec you couldn't have…

SaaS Isn’t Dead. Sameness Is.

Everyone suddenly wants to tell you SaaS is dead. The Klarna-replaced-Salesforce story spread because it fit the mood, whatever the exact boundary between “replaced the CRM” and “consolidated the data underneath it” turned out to be. Agents are starting to do work that once required people clicking through applications. Seat-based pricing looks vulnerable when the seats themselves stop doing the work. Even Gartner now estimates that as much as $234 billion in enterprise application spending could be exposed to what it calls “agentic arbitrage” by 2030, roughly 20 percent of enterprise SaaS spending. But Gartner itself hedges the apocalypse. It calls what is coming less an apocalypse than a metamorphosis. I think that’s right. And I want to make a more specific prediction about what SaaS turns into. Companies will still pay other companies to host important software, secure it, operate it, maintain authoritative data, track regulatory changes, connect to external networks, and answer the phone when something breaks. What is much less obviously going to survive is the assumption that ten thousand different companies should use substantially the same application. The next SaaS company may run one service underneath ten thousand different applications. We standardized software because variation was expensive Suppose your company has an unusual sales process. Twenty years ago you had two realistic options. You could buy a CRM designed for thousands of companies and configure it until it approximately fit the way you worked. Or you could build your own. The second option sounded appealing until somebody calculated what “build your own” meant: developers, designers, product management, security, hosting, integrations, upgrades, migrations, support, on-call coverage, and years…

Most of Your Architecture Was Just Expensive Code

For decades, architectural knowledge lived inside the implementation, because the implementation was expensive to reproduce. That expense is disappearing. And as it goes, we're going to find out how much of what we called "architecture" was really the cost of change wearing a disguise. Cheap code is supposed to make architecture matter less. It's doing the opposite. As implementation gets cheap, architecture becomes both more important and, for the first time, visible. The argument about AI coding has moved fast. A year or two ago, the interesting question was whether models could write useful software at all. Then it became whether they could complete features, navigate repositories, run tests, fix their own mistakes, and work for long stretches without someone watching over their shoulder. For anyone working seriously with the current generation of tools, that argument is mostly over. They can produce a lot of code. Sometimes an astonishing amount. The argument now is about what happens afterward. You hear it more and more from people at the frontier of these tools: code generation is no longer the bottleneck. Verification is. The same conversations keep circling back to harness engineering, cognitive debt, apprenticeship, and whether architecture and design still matter when the programmer is increasingly an agent rather than a person. Martin Fowler has framed a live disagreement here. Maybe sufficiently capable models will eventually deal with whatever spaghetti we hand them. Or maybe the properties that make systems understandable to humans (modularity, naming, clear boundaries) matter just as much to agents. Meanwhile, the people actually attempting highly autonomous software development are running into the walls. Dex Horthy of HumanLayer has described trying to…

The Specification Is Not a Document

Imagine joining a software project a few years from now. It is a large, mature system. It has thousands of business rules, years of operational experience, regulatory obligations, performance constraints, strange edge cases, customer commitments, and architectural decisions made by people who no longer work there. You ask the obvious question. "Where's the specification?" Someone gives you access to it. There is no SPEC.md. No 900-page requirements document. No canonical YAML file. No giant prompt that somebody feeds to an agent. There is not even a directory you can browse and reasonably call the specification. So you ask how you are supposed to understand the system. The answer is another question: "What do you want to know?" What is this system for? Who depends on it, and what promises does it make to them? Why does checkout retry a failed authorization three times? Which parts of the system are affected by European data residency requirements? What did production teach us in the last twelve months? Which architectural decisions still depend on assumptions that are no longer true? Each question produces a different view of the same underlying body of knowledge. It is tempting to imagine that somewhere behind this interface there must still be a canonical specification: some master representation that contains the whole system in its definitive form. But that may be the wrong assumption too. The durable knowledge of a system might not have one natural representation at all. Some of it may be formal, some executable, some observational, some textual. What matters is that these pieces can be identified, related, queried, and traced back to their sources. I don’t think there is a single thing underneath all of this that a person could sensibly open and inspect from…

When Does a Specification Become a Program?

Someone asked me recently where the boundary lies between a specification and an implementation. The question has been floating around a lot this year. Gabriella Gonzalez put it directly in an essay titled A sufficiently detailed spec is code. Her argument is partly economic: if producing a specification precise enough for an agent requires doing essentially the same intellectual work as programming, we haven't escaped the hard part. Mario Zechner made a related point in his AI Engineer talk Building Pi in a World of Slop: whatever you leave unspecified gets filled in by the model, and those decisions are no longer yours. Both objections matter for regenerative software. Imagine starting with an English description of a system. It isn't precise enough, so we add requirements. Then invariants. Examples. Schemas. Conditions. State machines. At some point pseudocode seems clearer than another paragraph of prose. Eventually we may include executable fragments because they express some requirement more accurately than English can. Keep going and the specification starts looking suspiciously like a program. But there are really two questions hiding here. How much work does it take to specify a system precisely enough that we trust what gets generated? And once our durable description contains prose, tests, schemas, formal constraints, executable examples, and maybe actual code, what distinguishes that description from the implementation itself? The first question is about whether specification actually saves intellectual labor. The second is about where the identity of a regenerative system lives. I'm interested in both, but especially the second. There will be no perfect specification One seductive version of AI-native development starts with an oracle. Describe the desired…

The Implementation Remembers

Every mature system carries lessons no one remembers writing down. They live in the dark corners of the codebase: arbitrary retries, weirdly specific timeouts, defensive checks, validation rules, special-case workflows, and if statements that look wrong until the day you remove them. From the outside, this stuff looks like junk. It looks like accidental complexity. Sometimes it is. More often, it is scar tissue. A Friday deployment went sideways. A dependency failed in a way the vendor said was impossible. A customer found a path through the product that nobody had modeled. A regulator, partner, or internal team imposed a requirement that never made it into the architecture diagram. The organization forgets the story. Teams turn over. The incident doc gets lost. The Slack thread disappears under years of chatter. The person who knew why that timeout has to be 17 seconds leaves. But the implementation remembers. A mature system is not just an expression of what we want the software to do today. It is a record of what happened when that desire collided with reality. This is one danger of treating code as disposable in the AI coding era. When we hand a model a messy function and say, “clean this up,” it will usually try to help. It will simplify. It will normalize. It will remove the strange-looking parts. Sometimes that is exactly what we want. But sometimes it removes the memory along with the mess. The problem is not that AI cannot write cleaner code. It can. The problem is that cleanliness and correctness are different properties. Clean code that forgets why it exists is just a more elegant way to fail. Before we regenerate, refactor, or replace a working system, we need to ask a different question: What does this implementation know that we have forgotten? The…

Production Is a Compiler Input

Production used to be the place where software went to fail. Observability made it the place where software becomes legible. But it left one loop open. We use production telemetry to debug incidents, explain behavior, gate rollouts, and decide whether to roll back. We use it to help humans understand reality. Then a person decides what the code change should be. Instead, production truth becomes an input to what the system generates next. The key idea is simple: the primary failure mode is not always code breakage. It is evidence decay. A component can satisfy the spec today and fail it three months from now even if nobody touches the code. Traffic shape changes. Data distribution shifts. Dependencies slow down. Fallback paths activate more often. Cost envelopes move. Latency ceilings stop holding. The implementation may be unchanged. The world is what changed. That is technical drift: when production evidence no longer supports the claim that an implementation satisfies the operational or business constraints attached to its spec. Not that a service got slower but that the implementation no longer satisfies the latency and cost envelope the requirement promised. Not that a dashboard got worse but that the evidence that justified this module is no longer valid. Once you see that, the role of observability changes. Charity Majors and others have been pushing toward this for years: production as the place where we learn the truth, observability as the ability to ask new questions of live systems, and production as the place where intent has to be validated against reality instead of against our hopes. See “You Had One Job,” “Observability: A Manifesto,” “Honeycomb 10 Year Manifesto: Observability in a World of AI,” and “Your Data is Made Powerful By Context.” Production…

The Phoenix Primitives

Thirty source files organized into layers. Controller, service, repository, data mapper. That's a typical microservice, and every one of those layers exists so a developer can navigate. You put the database logic in one place and the HTTP handling in another because a person needs to hold the shape of the system in their head. The file is a cognitive container. The module boundary reflects what a developer can reason about in a single sitting, not some deeper truth about the problem domain. When code is regenerated rather than maintained, those constraints lose their force. A regenerative system doesn't need navigation. It needs a specification precise enough to produce correct behavior. Those 30 files might be regenerated from a single behavioral spec, structured completely differently each time. The file layout is an output, not an input. This goes deeper than files. Branches, pull requests, code review: these are rituals organized around the assumption that a human authored each change and another human needs to verify it. They're valuable rituals. But they're contingent on a model of software production that's shifting underneath us. The file stops being a primitive. The specification becomes one. The architecture of a regenerative system is defined entirely by what you can't delete. That's the claim this essay is built around. Every architecture has primitives: irreducible building blocks you compose everything else from. Ours were shaped by decades of humans writing code by hand. When implementation becomes disposable, a different set of primitives emerges. The interesting question isn't "what can AI generate?" but rather what are the critical artifacts in a system designed to be continuously reborn? The Primitives Start with something concrete. A payment…

The Generative Stack

Every layer of the software toolchain is changing at the same time. New spec formats, new generation strategies, new evaluation frameworks, & new feedback mechanisms show up weekly, each one supposedly the one that matters. Most teams respond by trying to pick winners. Maybe I'm just old and too cranky to follow the trends day in and day out, but I think that's exactly wrong. What you actually want is an architecture where independent tools and representations coexist at every phase. Competing, reinforcing, composable. A system that absorbs change without requiring you to place a bet. Because pipelines that lock in today's best options? Those become next quarter's migration projects. I've seen it happen enough times to stop being surprised by it. That's what I mean by the generative stack. The Full Pipeline The generative stack covers every phase between a human's intent and a system's verified behavior. Not just specifications. Spec inputs (markdown documents, conversations, diagrams, existing code, domain models) feed into structured clauses. Canonicalization structures those clauses into requirements and invariants. Requirements drive evaluations. Evaluations constrain implementation units. Implementation units become actual code. Running code produces feedback (eval results, runtime behavior, performance data) that flows back into the loop, refining every upstream layer. Each of those phases is its own site of diversity. Each one can absorb independent tools, overlapping representations, several approaches. And the trick is you don't choose between them. You compose them. At the spec layer, you might combine natural language descriptions, example interactions, and formal constraints. No single artifact captures the full intent. Together, they triangulate it. At the…

The Conversation Is the Commit

Here’s a scene every working programmer has lived. It’s 2 AM. Something is broken in production. You’re staring at a commit from eight months ago. The message says “refactor auth logic.” That’s it. The PR has two approvals—thumbs-up emoji, no comments. The Slack thread where the team debated the approach was in a channel that got archived when the org restructured. The person who wrote it left the company in June. You don’t just need to understand what this code does. You need to understand why it exists in this form—what alternatives were considered, what constraints shaped it, what tradeoffs were accepted. That information isn’t missing. It was never captured. It lived in someone’s head. It passed through ephemeral media. It evaporated. We’ve always treated code as the primary artifact of software development. Everything else—the reasoning, the tradeoffs, the rejected approaches—is secondary. Informal. Optional. We write it down when we feel like it, in whatever format is convenient, with no expectation that it will survive contact with time. That was a defensible tradeoff when humans wrote all the code. The alternative—capturing every decision rigorously—was more expensive than the knowledge loss it prevented. Humans optimized for shipping. Documentation was a tax. It breaks completely when agents enter the picture. The Real Artifact When an agent writes code, something fundamental shifts. The code is no longer where decisions are made. It’s where decisions show up. The real work happens in the back-and-forth. You try something, it does something slightly wrong, you tighten the prompt, add a constraint, back it out, try again. That loop is the engineering. The code is just what falls out of that loop—the way a compiled binary falls out of source. Once you see it,…

Compile to Architecture

The industry is still trying to generate applications. A React app. A Django service. A Rails API. A FastAPI backend. That instinct made sense when writing software was the expensive part. But in a world where code can be generated quickly and cheaply, the real constraint has shifted. The problem is no longer producing code. The problem is replacing it safely. Regenerative software does not work if the unit of generation is an application. Regeneration only works if the unit of generation is a component that compiles into a system architecture. Languages and frameworks matter, but they are implementation choices. Architecture is the thing that actually determines whether a system can evolve without breaking itself. A System That Already Worked Years ago at Wunderlist we ended up with a system structure that, in hindsight, explains why replacing pieces of the system was easier than it should have been. The architecture settled into several layers: clients across multiple platforms, a WebSocket actor server acting as a synchronization proxy, a REST domain layer containing business logic, a data service layer in which each service owned its datastore, and a mutation bus that propagated changes through the system. The frameworks we used inside those services varied over time. But the structure of the system stayed consistent. Each layer had clear responsibilities. Every datastore had a single owner. Mutations moved through predictable paths instead of appearing unpredictably across the codebase. Once that structure existed, every new component had to fit within it. Services could be rewritten. Implementations could change. But the structure of the system remained stable. That stability is what made replacement safe. Frameworks Hide the Real Problem Frameworks are valuable…

The Regenerative Grain

From Tiny to Deletion Safety In 2014, I gave a talk called Tiny. The argument was simple: keep things small enough that a human can understand them. In 2026, that idea has evolved. Small is no longer primarily about cognitive load. Small is about deletion safety. When generation is abundant, the scarce resource is no longer typing. It is verification. It is traceability. It is knowing what must remain true when implementations change. Regeneration only works at the right grain. From Cognitive Load to Deletion Safety "Small" used to mean easier to reason about, faster to ship, less likely to explode. Now "small" means safe to delete, cheap to regenerate, and verifiable at a clean boundary. If deleting a component feels terrifying, the problem isn't courage. It's architecture. If you can't kill it, you don't own it. It owns you. This builds directly on earlier arguments in this series. The Deletion Test asked whether you could delete your entire codebase and regenerate it from specification. That was a diagnostic for the system as a whole. The regenerative grain applies the same logic at a finer resolution: individual components, services, and modules. Where the Deletion Test reveals whether your evaluations are strong enough to survive regeneration, the grain question reveals whether your boundaries are drawn in the right place. Tinyness isn't aesthetic minimalism. It is structural. It determines whether regeneration is safe or reckless. Finding the Grain Before you settle on a component boundary, imagine deleting it and recreating it from its specification. Not refactoring it. Not patching it. Deleting it. If that thought makes you sweat, the grain is wrong. This diagnostic isn't a law of physics. It's a heuristic. But it's a reliable one. And unlike a one-time design…

The Industrialization of Regenerative Software

The “AI software factory” metaphor is seductive. Factories increase output. They reduce marginal cost. They turn craft into production. For decades, writing code was the bottleneck. Now generation is cheap, and it feels like we’ve industrialized software. But factories are not optimized for throughput alone. Real factories are optimized for yield. Throughput is how much you produce. Yield is how much of it survives. That distinction matters more than most of the AI discourse admits. A team embraces the factory model. In two weeks, they’ve generated forty internal services: shared utilities, validation layers, orchestration glue, observability hooks. Everything modular. Everything neat. Three months later, something subtle shifts. A small requirement change touches validation. Five different modules contain similar logic. No one remembers which one is canonical. The tests still pass because they verify each implementation, not the invariant across them. Monitoring disagrees with itself. Metrics drift, but not catastrophically. There’s no outage. Just friction. Deleting anything feels dangerous. Everything might be referenced somewhere. No artifact looks obviously wrong. Nothing is clearly scrap. They didn’t move too fast. They industrialized generation without industrializing forgetting, and nothing in their tools or culture warned them they needed to. Manufacturing learned this lesson long ago. A factory that optimizes only for throughput accumulates waste. Defective units pile up. Rework increases. Inventory grows. Costs hide inside buffers. So industrial systems built discipline around scrap: defect rates are measured, processes are retooled, material is reclaimed, disposal is designed into production. Yield becomes the signal that matters. Software factories might…

The Deletion Test

Here’s a simple test you can apply to any software system you work on: Imagine deleting the entire implementation. Not refactoring it. Not archiving it. Not putting it behind a feature flag. Deleting it. rm -rf src/ If that thought makes your stomach drop, pay attention. That reaction is telling you something important. It’s not telling you that you’re reckless. It’s not telling you that you lack discipline. It’s telling you that you don’t know what would survive. Fear Is a Signal Most engineers experience deletion as existential. Code feels like the thing. It’s what we write, review, version, deploy, and debug. Losing it feels like losing the system itself. But that fear is not inevitable. It’s contingent. There are systems where deleting the implementation would be inconvenient but not terrifying. You might lose time. You might burn compute. But you wouldn’t lose understanding. The difference is not bravery or craftsmanship. It’s where meaning lives. What Are You Actually Afraid of Losing? When people say, “We can’t just throw the code away,” what they usually mean is something more precise: We don’t know exactly what behavior is required. We don’t know which failures are unacceptable. We don’t know what invariants must always hold. We don’t know how to tell if a new version is correct. We don’t know which bugs are intentional fixes for forgotten edge cases. Those are not code problems. They are evaluation problems. Code becomes precious when it is the only place knowledge lives. Code as a Stand-In for Understanding For most of software history, treating code as durable was reasonable. We treated code as permanent because the labor to produce it was the bottleneck. Rewriting was expensive. Re-validation was risky. Implementations accumulated meaning over time.…

UI Is a Conservation Layer

If you’ve been following this series, you may already be thinking: “This all makes sense for non-UI code. But surely this can’t apply to interfaces.” That reaction should feel familiar. It’s the same one people had earlier when I argued for regeneration and got back: “Regenerating all the code every time is crazy.” It sounded reasonable then, and it sounds reasonable now. In both cases, the objection comes from the same category error. Regeneration does not mean indiscriminate churn. It means bounded replacement behind stable interfaces. When you miss the boundary, the idea sounds reckless. When you see the boundary, it becomes conservative. UI is arguably where that boundary matters most. The Category Error, Revisited Regeneration works extremely well for large parts of modern systems: infrastructure services domain logic state management non-UI code inside client applications All of that can change rapidly without confusing users, as long as the system’s human-facing behavior remains coherent. The mistake is assuming the UI belongs in the same category. UI is not just another artifact. It is the human-facing boundary of the system. It is where people form habits, build expectations, and decide whether they trust what they’re using. Treating UI as just another regenerable component ignores the cost of breaking those things. What UI Actually Represents Users don’t experience systems as implementations. They experience them as continuity. They learn where things are. They internalize flows. They stop thinking and start acting. That’s not cosmetic. That’s learned behavior. When you change UI, you’re not just changing pixels. You’re invalidating a mental model that took time to form. That cost is paid by every user, every time. Backend regeneration optimizes for…

Provenance Is the New Version Control

When code can be thrown away and recreated, the unit of change is no longer lines of code. It’s reasons. Version control has to follow. Regenerable systems quietly invalidate an assumption that has underpinned software engineering for decades: that the text of the code is the best record of how and why a system came to be. Once an AI can reliably regenerate an implementation from specification, the code itself becomes an artifact of synthesis, not the locus of intent. By regenerable, I mean: if you delete a component, you can recreate it from stored intent (requirements, constraints, and decisions) with the same behavior and integration guarantees. In that world, version control doesn’t disappear, but it has to move upstream. When Diffs Stop Representing Decisions Traditional version control works because code edits are a reasonable proxy for human decisions. Someone typed this conditional. Someone refactored that loop. A diff is an imperfect but serviceable record of authorship. AI-assisted generation severs that link. When an agent reads a specification, reasons about constraints, chooses an approach, and emits code, the resulting text reflects outcomes, not decisions. A diff can show what changed in the artifact, but it cannot explain which requirement demanded the change, which constraint shaped it, or which tradeoff caused one structure to be chosen over another. This is the sense in which code-first version control becomes a lossy history. Not because diffs are useless (they still matter operationally) but because they no longer represent the causal history of the system. They tell you what happened, not why it happened. That distinction matters once code is no longer directly authored. Specifications as Executable Intent In a regenerable system, specifications…

n=1 Is a Design Constrain (Not a Staffing Model)

Here is a design constraint worth taking seriously: if your system cannot be understood, modified, and regenerated from specification by one competent engineer, it is already too complex. This is not a statement about staffing. It is a statement about architecture. Call it n=1 capability. The claim is not that you should run your engineering organization with one person. The claim is that you should design systems where one person could. That’s the test. Why This Constraint Matters Systems that pass the n=1 test have specific properties: clear boundaries, externalized meaning, replaceable components, low coordination overhead. These are the properties you actually want. They scale judgment, not headcount. A system that requires a team is not necessarily bad. But a system that requires a team just to understand it—where no single person can hold its shape in mind—has a problem that will compound. Every new engineer slows down. Every departure creates knowledge gaps. Every change requires negotiation across boundaries that exist only in people’s heads. n=1 capability is the diagnostic. When one person can ship what used to require a team, it tells you something important about the system, not the individual. The Myth of the Solo Genius Software culture has a long history of mythologizing exceptional individuals. We tell stories about lone hackers and 10x developers, and we treat outsized impact as evidence of rare talent. That framing is comforting. It is also misleading. Not to mention the fact that I have rarely worked with a “10x developer” that wasn’t also somehow a tax on the team, project, or system, a single developer can only be effective to the extent that the system allows them to be. No amount of skill lets one person safely reason about a sprawling, entangled…

Relocating Rigor

In late 1999, I found myself inside Extreme Programming movement before most people had heard of it. Kent Beck's white "extreme programming explained" book had just come out. Ward Cunningham's wiki was where the real conversations happened. The Agile Manifesto wouldn't exist for another couple of years. From the outside, what we were doing looked reckless. We threw away long-range plans. We rejected heavyweight processes. We stopped pretending we could predict the shape of a system months in advance. We paired constantly, which looked inefficient. We wrote tests before code, which looked backward. We released continuously, which looked dangerous. To many observers, this was a removal of constraints. The opposite was true. XP compressed feedback loops until truth became unavoidable. Tests replaced promises. Continuous integration replaced status reports. Working software replaced narrative. You could no longer hide behind process because the system itself reported your progress, loudly and continuously. The practices that looked like chaos were actually mechanisms for enforcing honesty. Pair programming meant every line of code had a witness. Test-first meant you couldn't ship wishes. Short iterations meant you couldn't hide. The discipline was more demanding than what came before, not less. It just didn't look like the discipline people were used to seeing. That experience permanently changed how I think about software. It also explains why I lost interest once Extreme Programming got absorbed into the broader "Agile" movement and solidified into branding and ceremony. When the name took over, the rigor drained out. The feedback softened. The theater returned. Consultants taught the artifacts without the discipline. I wrote about that years ago in The Curse of a Name.…

The System Is the Asset

We’ve spent decades talking as if “the system” and “the codebase” were the same thing. They are not. A system is defined by its behavior, its interfaces, its data, and its invariants. Code is just one way, the historically dominant way, of expressing those things. When people hear “throw the code away” and assume “throw the system away,” they are conflating two very different acts. That conflation is the source of most of the resistance to these ideas. So let’s be precise about the distinction. What Actually Persists Look at any system that has survived for a long time, not because it was beautiful, but because it worked. What endured was never the exact implementation, the original language, or the clever abstractions. What endured was stable interfaces, well-understood behavior, data continuity, and a clear sense of what must not break. The system’s identity lived outside the code. The code was replaced far more often than people like to admit, sometimes explicitly, sometimes by accretion. The system survived because something else held it together. In retrospect, this was always true. We just did not have the tools or the economics to act on it deliberately. That something else is what we should be designing for. Local Replacement, Not Global Amnesia No serious architecture advocates “start over every time.” That idea collapses under even casual scrutiny. What does work, and has worked for a long time, is targeted replacement behind stable boundaries. This is the same logic that made immutable infrastructure viable. You do not throw away the service; you replace the instance. Identity lives at the service boundary, not the machine. Applying this to software means the system remains intact. The contracts remain intact. The behavior remains intact. The data remains…

Conceptual Mass and the Compaction Discipline

As I mentioned in a previous post, at Wunderlist, we had a rule: any new service had to be "this big", a constraint I'd demonstrate by holding my fingers a few inches apart. The metric wasn't about lines of code. It was about replaceability. If a service was small enough to rewrite in a day, it couldn't accumulate the kind of complexity that makes systems brittle. That rule was about resisting growth. Not preventing change but resisting mass. Every software system naturally grows. When change is easy and addition is cheap, structure accumulates unless something pushes back. For most of software history, that counterforce was human effort. Writing code was slow. Adding complexity hurt. Growth had friction. Generative AI removes that friction. Without an opposing discipline, AI doesn't just accelerate development. It accelerates bloat. This post is about the discipline that prevents success from turning into system weight. Accumulation Is the Default Failure Mode In AI-accelerated systems, expansion is the path of least resistance. Generation is cheap. Preservation is emotionally easy. Deletion requires justification. Think about how many times you've seen commented out code in a legacy code base where someone couldn't bring themselves to outright delete it even though it's not used anymore. That's the psychology we're dealing with here. Modern LLM-driven workflows strongly favor addition: new features appear instantly, glue code materializes, abstractions proliferate because the model has seen them before. Edge cases get special handling instead of root-cause fixes. "Temporary" code survives because it works. None of this requires bad engineers. It barely requires engineers at all. If you do nothing, your system will grow until it becomes unmanageable. This was true…

Immutable Infrastructure, Immutable Code

In 2013, I wrote about trashing servers and burning code. The argument was simple: systems that mutate while running accumulate state, history, and uncertainty in ways humans can't reason about. When something breaks, nobody knows which change caused it or what the system actually is anymore. So we stopped patching servers and started replacing them. We built machines that could burn down and rise again, identical in behavior, without human intervention. The server wasn't the thing. The capability to regenerate was the thing. That was an infrastructure principle, but it has always felt true to me for software. I was CTO for the company behind the popular Wunderlist productivity tool at the time, and as CTO I came up with a simple set of rules for choosing technologies we deployed at work: anyone can decide to use any new language or framework they want, but it must work with our build system, it must work with our deployment system, they must find at least one other person on the team to work on it with them and support it if necessary and (this is the most important part) the code has to be no more than "this big", which I'd say while holding up my hand with my fingers spread apart a few inches. That last part constrained the code in such a way that the worst thing that could happen with a new language or technology is that it crashed, nobody on call was able to fix it, and it would be trivial to rewrite and replace. And we did that sometimes. Code could even be treated like cells. As humans, parts of our biological material are dying all the time, yet the system (our body, brain, mind) remains. So today, if code can be regenerated cheaply, perhaps upgrading code in place is the antipattern. Infrastructure Figured This Out First Immutable infrastructure wasn't adopted…

Evaluations Are the Real Codebase

If deleting your codebase feels terrifying, your evaluations are insufficient. That's not a moral failure. It's a technical one—and in the age of AI-assisted development, it's an increasingly expensive one. Here is the shift: language models have made code generation cheap. Not free, not perfect, but cheap enough that regenerating a service is often faster than understanding and modifying it. This changes what counts as a durable asset. Code isn't it. Code is now a materialized view of understanding—useful while current, disposable when stale. The durable asset is the thing that lets you regenerate with confidence: evaluations that encode what the system must do, independent of how any particular implementation does it. Code is Cache Traditional software culture treated code as the memory of the system. It encoded intent, explained decisions, and preserved behavior over time. Protecting it was rational because replacing it was expensive. That expense has collapsed. When a model can produce working code from a description in minutes, the calculus inverts. Keeping code around "just in case" stops being wisdom and starts being hoarding. The implementation is a cache. It's a snapshot of your current understanding, useful for running in production, not precious in itself. If you delete a codebase and can't confidently regenerate it, that's not a tragedy. It's a diagnosis. The problem wasn't the deletion. The problem was that nothing important lived outside the code. The intent, the constraints, the behavioral requirements were all implicit in the implementation rather than explicit in artifacts that survive the implementation's death. The Spectrum of Test Durability Most engineers, asked how they ensure a system works, answer "tests." But tests vary enormously in what they…

The Gradient of Trust

If you've been using generative AI regularly for a while, you already know this feeling. There are classes of code you'll happily accept without even reading. A small, pure function. Statically typed inputs and outputs. A well-understood transformation. No I/O. No hidden state. No ambiguity. The AI writes it, you paste it in, and you move on with your life. And then there's code that touches the network. Code that encodes business rules. Code that depends on unclear invariants, partial documentation, or "everyone knows how this works." That's where things get weird fast. You reread it. You test it. You argue with it. Sometimes you rewrite it entirely. What's interesting isn't that these two poles exist. It's the gradient between them. Over time, developers build an intuition for where a piece of code sits on that gradient. Some code you trust immediately. Some code you trust only after careful review. Some code you never quite trust, no matter who wrote it. Once you notice that gradient, an obvious question appears: how do we design systems so that more of the code lives on the side where trust is easy? Constraints as Trust One of silly old jokes I'd tell from a system I worked on years ago was: "If you can make the Haskell system compile, it works." That's not actually true, of course, but it points at something real. A strong type system, purity by default, and explicit handling of effects dramatically shrink the space of possible mistakes. You trust the code not because you've verified it, but because the structure makes it hard to get wrong. This was always valuable. AI makes it load-bearing. If a function is small, pure, and tightly specified, it doesn't really matter whether it was written by a senior engineer, a junior engineer, or an LLM. The structure…

Compaction Is a Financial Strategy

The cheapest system in the AI era is not the one that never changes. It is the one who parts can be cheaply regenerated because they are small and decoupled. This claim sounds like architecture-conference wisdom, the kind of thing consultants say to justify rewrites. But something has shifted. The emergence of AI-assisted development has transformed code size from an aesthetic concern into a direct economic variable. Context windows have budgets. Tokens cost money. Every line of code you keep is a line you pay to process, again and again, every time you ask a model to reason about your system. Compaction—the deliberate practice of making systems smaller—was always the quiet secret behind sustainable software. AI has simply made the economics impossible to ignore. The Hidden Cost of Keeping Code Most organizations dramatically underestimate how expensive it is to keep code. Not to write it—that cost is visible in salaries and sprints. Not to run it—that cost shows up in hosting bills. The hidden expense is keeping it: the ongoing cognitive and computational tax imposed by code that exists. Consider what happens every time an engineer touches a large codebase. Before they can make a change, they must build a mental model of the relevant subsystems. This takes time—sometimes hours, sometimes days. The more code exists, the longer this ramp takes. Senior engineers hesitate to modify things they don't fully understand. Junior engineers make changes without understanding, introducing subtle bugs. The phrase "I'm not sure what this does, so I won't touch it" represents real operational risk, but it rarely appears in any budget. AI compounds this problem in a new way. When a model assists with development, it reasons over whatever context you provide. Large codebases exceed…