AI is an Expansion Engine. Software Engineering Needs a Pruning Engine

Why LLM Coding Fails Without Selective Pressure

Posted on by Adam Wespiser

AI Expansion vs Software Pruning

I tried to use AI to build a simple app.

The task wasn’t unusual: a data ingestion flow in a legacy system. A variety of data shapes, a series of transformations, validation logic, and a few API and LLM calls. The kind of project I’ve built, tested, and deployed many times over the last decade.

At first, using AI felt like a jet pack.

Then something strange happened:

The more I used the model, the worse the development process became.

It didn’t feel sharp and precise like coding should. It felt like losing control.

Eventually, I realized what was happening:

AI optimizes for expansion. But software development requires convergence.

The loop

I had fallen into a loop:

Prompt → Expand → Patch → Break → Repeat

Each incremental step felt like the next right thing. The system kept growing. But it was not getting simpler, clearer, or more correct.

Why AI fails here

AI makes it cheap to add:

But it does not make it easy to:

This matters because software is more than a pile of locally reasonable changes. It is a globally constrained system built for its effects.

LLMs are very good at producing local coherence.
Software requires global constraint.

Each individual addition can look reasonable. But if your process is only additive, it will never converge.

The missing step

The bottleneck isn’t generating code. It’s stopping the model from endlessly generating more.

Developing software requires the discipline to say no: to prune extra features, reject useless abstractions, and remove code that hasn’t earned its place.

Without negative pressure, coding with AI feels like progress while the system quietly drifts toward bloat.

What actually worked

AI coding improved when I forced constraints into the process:

In short: constrain the system until it cannot expand arbitrarily.

A concrete example

To test this approach, I built a small project: GitHub and a working demo.

The app is a financial and retirement simulator built to answer a simple question:

For a given retirement strategy, what does uncertainty in market returns actually mean?

To develop the app, I used a document-driven process to codify decisions into immutable artifacts:

  1. Write a spec.md that defines user behavior and system constraints.
  2. Create an architecture.md to formalize the code structure.
  3. Have the model produce a system-understanding-summary.md to prove it understands the spec.
  4. Build an implementation-plan.md with small steps, each tied to a verification test and scoped to be a “reasonable ask” for a junior engineer.
  5. Keep an append-only decision-log.md and progress-log.md so context lives in files, not in the prompt history.
  6. Prompt the agent to complete the spec section by section, following the coding rules in AGENT.md.

The important part was not “using better AI.” It was introducing a structure that puts selective pressure on generative output.

Once I stopped treating the model like an autonomous builder and started treating it more like a junior engineer, the process became much more reliable.

Conclusion

AI is an expansion engine. Software engineering is a pruning process.

Traditional development was constrained by time, effort, performance, and real user needs. These constraints acted as natural filters. Not every idea made it into the system.

LLMs remove these filters. New code is cheap. Ideas are easy to try. Tokens are monetized. Expansion is all but inevitable.

Left unconstrained, this doesn’t produce better systems—it produces drift.

If we want to ship production software, we now have to impose constraints ourselves. We have to create the selective pressure that used to come for free and is now too easy to ignore.

In the end, good software isn’t what you choose to build. It’s what you choose not to keep.