Skip to content
Back to all field notes vibe · 6 min

Every new feature breaks an old one: the missing contract

You add feature six and feature two stops working. It is not carelessness. It is a missing contract between the parts, and agents make it worse.

field note 6 min

You add a feature. Something unrelated stops working. You fix that, and a third thing breaks. Every step forward costs you a step somewhere you were not looking, and the app that once grew in minutes now moves like it is wading through mud. If you have felt this, you are not being careless. You are missing a specific thing, and it has a name.

The missing thing is a contract between your features. Nothing in the app says what each part promises to the others, so nothing stops one part from quietly violating another’s assumptions. Add enough parts and collisions are not bad luck, they are arithmetic.

Why this happens, precisely

Each feature in a vibe-coded app was generated more or less on its own. The agent held that feature in view, made it work, and assumed the rest of the app would hold still around it. That assumption is almost always unstated, and it is almost always false.

Feature two assumed the user record had a certain shape. Feature six changed that shape to do its own job. Nothing recorded that feature two was depending on the old shape, so nothing warned anyone. Feature two is now broken, and it will stay broken until a user finds it, because there was no contract to violate loudly.

Multiply this across a growing app. The number of ways features can quietly depend on each other grows much faster than the number of features. That is why the pain is mild at first and then, past a handful of features, seems to explode. It is the same compounding that produces the reckoning around month 3: the interactions, not the features, are what overwhelm you.

Why agents make it sharper

A human building slowly tends to carry an informal contract in their head. They remember that feature two cares about the user shape, so when they touch it for feature six, a quiet alarm goes off. It is fragile and it does not scale, but it exists.

An agent has no such memory across sessions. Each run starts fresh, sees the code as it is now, and cannot know what an earlier feature was silently counting on unless something in the codebase says so. The agent is not careless. It is working exactly as instructed, blind to a promise nobody wrote down. Faster generation just means you reach the collision zone sooner. This is one face of the broader truth that locally correct changes produce a globally incoherent system: each edit is fine on its own and the whole drifts out of agreement.

The fix is not “be more careful”

You cannot solve this by trying harder to remember. Memory is exactly the thing that does not scale here, and if you are using agents, the memory you would rely on is not even in the room. Care is not a mechanism.

The fix is to make the contract explicit, so that a violation is visible instead of silent. There are two layers to it, and you want both.

The contract as a written spec. Each feature should have a small, plain statement of what it promises and what it depends on: this feature guarantees this total always balances, this feature assumes the user record has a role. When those promises are written next to the code, a change that would break one has something to run into. This is what it means for the spec to be the contract rather than a document nobody opens: it is the thing that says what must stay true, so a violation is a broken rule instead of a surprise in production.

The contract as an executable check. A written promise still needs teeth. The teeth are characterization tests that pin each feature’s guaranteed behavior, so that when a later change violates the promise, a test fails immediately instead of a user filing a bug in three weeks. The written contract says what is promised; the test enforces it the moment it is broken. A green build alone does not prove any of this; the check has to actually exercise the behavior the contract names.

Together these turn a silent collision into a loud, immediate, located failure, at the moment you can still cheaply fix it.

Installing the contract on an app you already have

You do not need a rewrite to get this. You install the contract on the running app, in the same safe order that any reinforcement follows.

Write down what each of your existing features promises and assumes, starting with the ones that hurt most when they break. Pin those promises with tests that fail on violation. Then, as you add features, the collisions announce themselves during development instead of ambushing users in production. Over time the app stops being a place where progress costs regressions. The full staged version of this, for a whole app rather than one feature, is the crossing in from vibe coding to production, and the specific mechanics of reinforcing a fragile app in place are in fixing a vibe-coded app without a rewrite.

Where PaellaDoc fits

Keeping every feature’s contract in your head is exactly the job that fails as the app grows, and it is the job you are silently doing every time you brace before a change. PaellaDoc’s role is to hold those contracts outside your head: it keeps each feature’s specification attached to the code that implements it, and re-runs the app to check that a change did not break a promise some other feature was depending on. The point is not more discipline from you. It is that the contract between features lives somewhere durable and gets checked automatically, so the next feature is an addition instead of a collision.

New features are supposed to add. When each one subtracts somewhere else, you do not have a discipline problem. You have a missing contract, and contracts can be installed.

FAQ

Why does every new feature break an old one in my AI-built app? Each feature was generated assuming the rest of the app holds still, and that assumption was never written down. When a later feature changes something an earlier one depended on, nothing warns anyone, because there was no contract to violate loudly.

Why do coding agents make this worse? An agent has no memory across sessions of what earlier features silently depend on. Unless the codebase states those dependencies, the agent cannot know it is breaking one. Faster generation just reaches the collision zone sooner.

How do I stop features from breaking each other? Make the contract explicit: write down what each feature promises and depends on, and pin those promises with tests that fail the instant a change violates them. That turns a silent production break into an immediate, located failure during development.