Richard Gabriel wrote "The Rise of Worse Is Better" to make his own side look bad. He caricatured the Unix and C school of design, called it the New Jersey approach, and admitted in the same paper that it would win. I reread it because agents have changed how much effort I spend implementing software. I wanted to know whether that changes the design choices his essay defended. If a more complete implementation is cheaper to produce, the case for accepting less should weaken.
My first answer was that simplicity still wins because someone has to review the result. That was too narrow. A simple implementation can leave every caller with more to understand, test, and maintain. A more sophisticated implementation can take some of that work away.
Cheaper generation makes both choices easier. An agent can write one shared capability with a dependable contract, or a separate workaround for every team that asks. How fast the code appeared won't tell us which choice helped.
Judge simplicity by the total work required to verify, use, change, and operate the system, subject to the guarantees it must provide.
Small implementations still have a place. The change is that I stop judging them in isolation.
Same qualities, different trade-offs
In The Rise of Worse Is Better, Gabriel contrasts two approaches that both value simplicity, correctness, consistency, and completeness. They disagree about what to sacrifice. His MIT approach puts interface simplicity first and permits implementation complexity to protect it. His New Jersey approach puts implementation simplicity ahead of every other quality and will trade completeness for any of them. Gabriel caricatures New Jersey on purpose, then argues that it has better prospects for adoption.
His syscall story makes the distinction concrete. After an interruption, the MIT solution would restart the system call without additional caller logic. The Unix solution in the essay returns an error when the operation cannot complete and leaves the caller to decide whether to retry. The implementation avoids work that every caller must then handle.
Gabriel's economics include implementation time, portability, modest hardware requirements, and adoption that attracts further improvement. Implementation cost was explicit in his argument, and so was the guardrail: a limited initial solution must be fundamentally sound.
My question is narrower than whether his whole historical argument survives. When implementation gets cheaper, which of those trade-offs should we reconsider?
Cheaper implementation does not settle the design
In Everyone Knows You Never Rewrite, I argued that lower implementation costs can make previously uneconomic work worth considering. The condition was a trustworthy behavioral reference, something more substantial than the agent's confidence that its new version works. That condition holds here too.
Code that attempts to handle an edge case still has to be shown to handle it correctly. Cheaper generation doesn't prove that the difficult final work has disappeared. Verification costs move too, since agents can help produce tests and investigate failures. But a saving in implementation effort tells us nothing about how much verification work remains.
Consider an agent offering to support another configuration mode. The implementation might take minutes to generate. Before accepting it, I still ask which combinations need testing, what compatibility we're promising, and who will diagnose failures. The feature costs what it costs to test and support, however fast it arrived.
Completeness is now a decision someone has to make on purpose. Features that once required an explicit investment arrive as small additions to an agent session. I don't want the convenience of generating them to substitute for deciding whether they belong in the product. The counterargument is that sometimes the additional behavior eliminates work elsewhere, which is my strongest reason to stop equating simplicity with a smaller implementation.
Simple for the component, expensive for its callers
AWS gives a useful example in Making retries safe with idempotent APIs. A client asks a service to create a resource and receives no response. The resource might exist, or the request might never have completed. Retrying without the right contract could create a duplicate.
An idempotent API lets the caller repeat the same request without additional side effects, within the contract's defined conditions. AWS describes a caller-provided request identifier that the service uses to recognize repeated requests. That simplifies client behavior and adds work to the service.

The service must distinguish a retry from a new request and preserve the relevant state. The contract must define how it handles delayed requests and reused identifiers. The complexity moved behind the interface; it did not disappear.
Absorbing this work into a shared capability reduces the total burden on its consumers. Each caller still has obligations, but none of them has to reconstruct the service's handling of duplicate requests on its own.
That complicates an implementation-first reading of Gabriel. Cheaper implementation strengthens the case for a better interface at least as much as it makes a limited implementation easier to ship.
It also fits the platform direction I described in The Golden Path Needs an Intent Interface. Developers should understand their workload's requirements and consequences. They shouldn't need to memorize every internal module, naming convention, and policy translation to use the platform correctly. A platform can take responsibility for those details, but only cheaply when its contracts, tests, documentation, and operating model are dependable.
For a proposed shared capability, I count the work this way:
| Whose work? | What the comparison must include |
|---|---|
| Component owners | Implementation, verification, maintenance, compatibility, and the evidence supporting the contract. |
| Callers and their reviewers | Integration, configuration, caller-side failure handling, and tests of their own use of the capability. |
| Operators | Diagnosis, recovery, upgrades, runtime resources, and the consequences of a shared failure. |
| Users | Decisions, workarounds, and recovery steps the interface still requires of them. |
A shared component can also become a dependency that delays changes or spreads one defect across many teams. Callers still need evidence that their integration works, even when the component has strong tests. The comparison has to include those costs. Otherwise "the platform handles it" is as incomplete as "the caller can just retry."
A small app makes the same trade-off
I can see both choices in System Headroom, the menu bar app I built to understand what was consuming my Mac's resources.
The App Store edition monitors processes and does not terminate them. My sandbox experiments ruled out the termination approaches I tested, so the quit controls live in the separate Direct edition. That is an explicit capability boundary rather than a control that appears to work until the platform refuses it.
Elsewhere, I added implementation complexity to make the app easier to use. A raw process list would have left users to work out which helpers belonged to which application, so the app groups related processes. The grouping logic follows process ancestry, handles helper-name conventions, and keeps a standalone fallback. It runs as pure Swift against test fixtures, separate from the system calls that collect the data.
The design matters more than the amount of code. I accepted additional logic to reduce the user's interpretation work, and I separated that logic so I could test it directly. Returning the raw process list would have simplified my implementation without solving the problem I built the app to solve.
The sandbox decision reduces capability. The grouping decision adds behavior behind a simpler interface. Both make sense once I consider the product and its responsibilities. A one-person app does not establish enterprise cost savings. Still, it gives me a concrete reason to be skeptical when "keep the implementation simple" is offered as a complete design argument.
Verification is more than reading the code
The reviewer still matters. Requiring a person to read every generated line, though, would contradict where my own practice has gone. In From Faster Code to Better Engineering, I described moving from reading every line toward reviewing consequential plans, testing running previews, and auditing samples. The scope of delegation grew as the evidence improved.
By verification I mean executable checks, independent review, operational testing, and accountable human judgment: evidence appropriate to the change and the consequences of getting it wrong. The goal is to make the relevant behavior understandable and the evidence sufficient to support a decision. Fitting every implementation into one person's memory is a goal I gave up on.
A repetitive change across many files can be easier to assess than a small change to a shared authorization rule. Diff size won't tell me which review needs more attention. I want to know what behavior changes, who depends on it, and which checks establish that the required protections remain intact. That's the operating principle behind Trust the Gate, Not the Actor: make repeated judgments explicit and executable rather than depending on concentration alone.
The checks themselves still need scrutiny. Tests generated from the same mistaken understanding as the implementation will confirm the mistake, and a second agent's approval doesn't resolve that. Expected behavior needs a defensible source: a reviewed requirement, a reliable existing implementation, or a known failure case. I trust a longer implementation with evidence behind it more than a short one without.
Reduce scope, not required guarantees
The phrase "drop the rare cases" makes me uncomfortable, because rarity doesn't tell us whether a behavior is optional. Choosing not to support another import format can be a legitimate scope decision. Losing saved work when an operation fails is a defect. Both involve unusual conditions, and only one is an optional capability.
I have a concrete reason to make that distinction. In the engineering retrospective, I described a database migration whose mocked tests passed while production behavior failed. A user lost local task data. I changed the tests to exercise an in-memory IndexedDB implementation rather than mocking away the behavior that mattered. The missing work was evidence for an obligation the product already had, and no amount of added product completeness would have substituted for it.
I apply Gabriel's four qualities this way today. This is my adaptation, not a ranking I attribute to his essay.
- Simplicity. Count the work across the component's owners, its callers and their reviewers, its operators, and its users. Prefer explicit contracts and designs whose important behavior can be checked without reconstructing the whole system.
- Correctness. Treat required behavior and protections as acceptance conditions within the supported scope. Narrow the scope when necessary, but never quietly weaken those conditions to make a change easier to approve.
- Consistency. Use predictable behavior to reduce what callers and reviewers must remember. Document necessary exceptions and test them. Removing a rare but required case only moves the surprise to the caller.
- Completeness. Add capability when its value justifies its verification, maintenance, and support obligations. Make unsupported behavior explicit. An agent's ability to generate another option isn't a product requirement.
This still leaves room for small, useful releases and improvement through real use. On one of my projects the installed base is me, which makes prioritization meetings easy. It doesn't remove the work of supporting what I add.
How I ask for the comparison
For a consequential design choice, I ask for more than "the simplest implementation that satisfies the spec." I ask the agent to compare where the work goes.

The specification states supported behavior, constraints, non-goals, and acceptance evidence. It is its own artifact, separate from the API and from the prompt used to draft it. Agents can help write the specification and the implementation; humans remain accountable for the requirements they approve.
A design request looks like this:
Compare designs against the same acceptance criteria. Include one that minimizes implementation complexity and one that reduces the work required of callers. Consider an existing supported component before proposing a new one. For each option, identify implementation effort, caller responsibilities, verification evidence, compatibility obligations, and recovery requirements. Recommend a design, state the assumptions behind the recommendation, and identify what we would need to measure to check it.
I don't turn every small change into an architecture exercise. The comparison earns its cost when a decision establishes a shared contract, adds a dependency, or commits us to ongoing behavior. When it does, the change has to show its behavioral consequences and supporting evidence alongside its diff. When that evidence is insufficient, we split the scope or improve the checks. Do not lower the acceptance criteria because generation has outpaced review.
For leaders, this is a funding decision. Shared contract tests, maintained components, and recovery exercises need owners and capacity. Their value includes the work they remove from consuming teams, and that responsibility continues after deployment. In Nobody Pages the Agent, I argued that verification must include operation and recovery. "Simple to operate" is a judgment the operators make, long after the deployment succeeded. I would rather fund that capability than make every reviewer compensate for its absence.
What would change my mind
The obvious objection is that this is familiar architecture advice. Count the whole system, make trade-offs explicit, and avoid unnecessary capability. None of it began with coding agents.
I agree. What agents change is the effort required to produce another implementation or another feature. That makes it easier to accumulate obligations without a deliberate decision about them, and it makes alternatives cheaper to investigate. We should use that to compare designs instead of assuming whichever one appeared first is good enough.
My working position is that additional implementation complexity is worthwhile when it removes repeated work and supports a dependable contract. I would reject that choice if consumers still needed extensive workarounds, maintenance costs exceeded the avoided effort, or shared failures became harder to recover from. For comparable changes, I track verification effort, integration rework, ongoing maintenance, and recovery performance. Lower review time is an improvement only when the extra risk it accepts is on the record.
The examples here do not establish enterprise-wide savings, and better automated verification could change which design is least expensive to support. The decision should follow the evidence rather than a permanent preference for either school.
I still prefer the smallest system that meets its obligations, once I count those obligations across everyone the design touches. Sometimes that favors a narrower product; sometimes it justifies more complexity behind a dependable interface. Cheaper code changes the cost of those choices. It does not make the choices for us.
