I wrote a 200-line prompt and got mediocre auth code. Then I wrote three words and five rejection rules. The second version was unrecognizable.
This is the story of how telling AI what to reject taught it more about my codebase than telling it what to build ever could.
There is a pattern every engineer hits eventually. You need a feature. Authentication, a data pipeline, a component. You sit down and write a detailed prompt. You describe the architecture, specify the patterns, maybe include example code. You give the AI everything it could possibly need.
The AI produces exactly what you described. The code runs. The tests pass. And something is off.
Not wrong in a way that breaks anything. Wrong in a way you feel in your chest. The code is correct and generic at the same time, like documentation examples wired together. Every function does what you asked, but none of them do what you meant.
So you try again. You add more detail to the prompt. More examples. More architecture diagrams described in prose. The output gets longer but it does not get better. The problem is not that the AI misunderstands your instructions. The problem is that it understands them perfectly, and follows them at the expense of everything else it already knows.
You are pouring instructions into a system that already knows how to write excellent code. Your instructions are the thing preventing it from doing so.
The shift happened by accident. I was building a hook system for Claude Code, a set of shell scripts that run before and after the AI writes code. Instead of writing another detailed prompt, I tried something different. Instead of telling the AI what to build, I told it what I would not accept.
Not "use branded types for IDs." Instead: reject any code where a raw string is used as an identifier. Not "write error handling with Result types." Instead: reject any function that can throw without the caller knowing.
The difference is structural. An instruction says "go here." A constraint says "not there." One gives the AI a destination. The other gives it a boundary, and everything inside that boundary is available for it to explore.
UserIdverifyToken() can
throw without Result wrapperResult<TokenPayload, AuthError>Same three-word prompt. "Add user authentication." But the constraints forced the AI through a different region of solution space entirely. The branded types, the Result wrappers, the error conventions, none of that was in the instruction. It was encoded in the rejection boundary. The AI already knew these patterns. My detailed prompt had been overwriting that knowledge with my own more limited version of how auth should work.
This is not a prompting technique. This is a way of thinking about building.
Test-Driven Development is the same insight wearing different clothes. You do not prove code correct by describing correctness. You prove it by writing tests that describe failure, then building code that survives them. The test does not say "make this work." The test says "this will break you if you are wrong." TDD does not constrain with instructions. It constrains with consequences. And the code that emerges from those consequences is structurally different from code that was merely told what to do.
Two hundred possible solutions, scattered across code space. Watch what happens when constraints activate. Each constraint name appears, and the solutions that violate it flash red and vanish. What survives is not restricted. It is revealed. The solutions that remain were always the excellent ones. The constraints did not create excellence. They made it visible.
In 1985, the Nintendo Entertainment System gave developers two kilobytes of working memory. Not megabytes. Two thousand bytes. Super Mario Bros. exists because of what could not fit.
Clouds and bushes in Super Mario are the same sprite, recolored. Not because Miyamoto lacked imagination, but because the memory constraint forced a specific kind of creativity: reuse that looks intentional. The constraint did not limit the game. It produced a visual language so iconic that modern games with unlimited memory still imitate it.
This is not an AI story. This is a pattern that shows up everywhere you look, once you know what to look for.
The United States Constitution is eight thousand words long. The Bill of Rights is shorter still. The framers did not describe a perfect society. They described what the government is not allowed to do. Freedom of speech is not an instruction to be eloquent. It is a rejection boundary: the state shall not cross this line. And from that boundary, an entire culture of expression emerged that no amount of positive instruction could have designed.
Your DNA works the same way. Less than two percent of the human genome codes for proteins. The rest, for decades dismissed as "junk DNA," is largely regulatory. It says no. It constrains when genes activate, in which tissues, at what developmental stage. The constraint machinery is forty times larger than the building machinery. Biology learned this lesson four billion years before we wrote our first hook.
Neural networks learn it through dropout. During training, random neurons are silenced, forced to zero. The constraint is brutal and seemingly arbitrary. And the network that survives dropout generalizes better than one trained without it, because the constraint forced redundancy. It forced the network to encode the signal in multiple paths rather than memorizing it in one.
The pattern repeats across every domain: constraint does not reduce the space. Constraint compresses the signal within the space. What survives constraint carries more information per unit than what was never constrained at all.
Watch the boundary line. Early particles (cyan) hit it directly, leaving permanent gold markers where they were rejected. As the markers accumulate, something changes. Later particles detect the encoded rejections and curve away before contact, turning green. They learned from the boundary without touching it. The boundary did not just reject. It taught. By the time the boundary is fully encoded, new particles navigate the space perfectly, guided entirely by information stored on the surface.
If constraints are this powerful, a question follows that most constraint enthusiasts never ask: when do constraints destroy?
The Linux Kernel Mailing List has some of the most rigorous review constraints in open source. No commit enters the kernel without passing through layers of rejection. Style checks, architecture review, performance analysis, security audit. These constraints produced one of the most reliable software systems on Earth.
They also produced one of the most toxic developer communities in existence. Linus Torvalds's review style was technically a constraint. It rejected bad code. But it also rejected people. The same mechanism that made NES sprites creative made kernel development hostile. Constraints encode their implementation, not their intent.
Toyota built just-in-time manufacturing into a philosophy. Every part arrives exactly when needed, zero inventory, maximum efficiency through constraint. In 2011, the Tohoku earthquake severed a single supplier link, and Toyota's production dropped forty percent worldwide. The constraint that eliminated waste had also eliminated resilience.
Your immune system is a constraint architecture. T-cells patrol for patterns that do not belong and destroy them. When the constraints are well-calibrated, you survive infections that would kill you without them. When the constraints miscalibrate, the immune system attacks the body's own tissue. Autoimmune disease is not a failure of the immune system. It is the immune system working perfectly on the wrong target.
Constraints encode their implementation, not their intent. The same architecture that teaches can imprison. The same mechanism that protects can destroy. Any framework that presents constraints as purely positive is selling you something. Including this one, if you stop reading here.
The difference between a constraint that teaches and a constraint that imprisons comes down to what it encodes. A branded-types constraint encodes a real architectural principle: identifiers have semantic meaning in this codebase. A max-lines constraint encodes nothing. It measures a surface property (line count) instead of a structural property (type safety, error handling, test coverage). Surface constraints trigger compliance. Structural constraints trigger thinking.
Left side: structural constraints. Each particle enters small and cyan. As it passes through each gold gate, it grows, turning brighter and shifting toward gold. It exits transformed, larger than it entered. Right side: surface constraints. Each particle enters the same way, but the red walls bounce it back and forth. With every collision it shrinks, dimmer with each impact, until it vanishes entirely. Same particle. Same mechanism. One side creates growth. The other creates death.
There is a name for what I have been describing. I did not plan it. I followed the pattern across domains, from hooks to TDD to Miyamoto to the Constitution to dropout to autoimmune disease, and the name arrived on its own.
Constraint Driven Development.
| TDD | BDD | CDD | |
|---|---|---|---|
| Constrains with | Tests | Behaviors | Rejection boundaries |
| Rejects | Code that fails tests | Systems that violate specs | Patterns that violate principles |
| Preserves | Everything that passes | Everything users accept | Everything the AI knows |
| Who discovers | Developer | Product team | The AI |
TDD constrains with tests. You write the failure condition first, then build code that survives it. BDD constrains with behaviors. You specify what the user should experience, then build the system that delivers it. CDD constrains with rejection boundaries. You specify what you will not accept, and the AI discovers everything else.
I did not choose the destination. I chose to follow the topology honestly, and the destination chose itself.
Here is how to start.
Ready-to-paste prompt to get started:
Your constraints are your codebase's immune system. Start with one. Let it teach. Add the next when the first is invisible. The constraints do not restrict the AI. They concentrate it. And if a constraint starts producing worse code instead of better code, it is a surface constraint pretending to be structural. Remove it before it becomes autoimmune.
The constraint created a space. The AI hit the boundary, restructured, and produced different code. But what happened in that space, between the rejection and the restructuring? Was the AI just pattern-matching a different template? Or was something closer to thinking happening in the gap?
Are the hooks improving the code, or are they teaching the AI to think?