There is a pattern I keep seeing with developers who are new to AI-assisted development.
They open a chat, describe the feature, let the AI generate the code, and move on when the tests pass.
That works right up until the feature contains one or two rules that are obvious to the business, but not obvious to the model.
Then the AI does what it always does. It fills in the gaps with the most common assumption.
The code looks good. The tests look good. The behavior is wrong.
That is the gap I am trying to close.
I do it with something I call an AI contract.
Why user stories are not enough for AI
A user story is great for communicating intent to a human developer.
As a user,
I want [feature]
So that [outcome]
A human reads that and brings context, experience, and judgment.
An AI reads that and brings pattern matching.
That is the difference.
If the feature is simple and conventional, the AI will often get close enough. If the feature has rules that differ from the obvious implementation, the AI will confidently build the wrong version and then write tests that confirm it.
That is why I do not give AI only a user story for logic-heavy work.
I give it a contract.
A very small example
Take a basic cart discount rule:
If the discount is greater than the cart subtotal, the total must be clamped to zero. It must never go negative.
This is not a complicated feature. It is tiny. But it is exactly the kind of rule an AI can get wrong if you describe it loosely.
A vague prompt might be:
Build a cart total calculator with subtotal, discount, and total.
That sounds harmless.
But it leaves one important question unstated: what happens when the discount is larger than the subtotal?
A naive implementation might produce this:
$total = $subtotal - $discount;
If subtotal = 1000 and discount = 1500, the total becomes -500.
The code is clean. The math is correct. The feature is wrong.
The correct behavior is:
$total = max($subtotal - $discount, 0);
That single rule is the difference between a valid checkout flow and nonsense.
This is the kind of thing I put into a contract before the AI starts writing code.
What an AI contract is
An AI contract is a small structured Markdown document that tells the model exactly what the feature must do.
Not in product language. In implementation language.
It does not need to be huge.
For a simple feature, I only need four things:
- what the feature does
- the hard rule
- a couple of concrete scenarios
- a verification step
That is enough to remove ambiguity.
A simpler contract format
Here is the lightweight version I use for smaller features.
# FEATURE_CONTRACT.md
## Feature
Calculate cart total from subtotal and discount.
## Hard rule
Total must never be negative.
If discount exceeds subtotal, total must be 0.
## Scenarios
SCENARIO-001
Input: subtotal=1000, discount=250
Expected: total=750
SCENARIO-002
Input: subtotal=1000, discount=1000
Expected: total=0
SCENARIO-003
Input: subtotal=1000, discount=1500
Expected: total=0
## Verification
- confirm total is clamped to zero minimum
- confirm all scenarios pass exactly
- report PASS or FAIL with evidence
That is it.
No giant template. No heavy process. Just enough structure to stop the AI from guessing.
Why this works better than a normal prompt
A normal prompt tells the AI what you want.
A contract tells the AI what counts as correct.
That is the key difference.
Without the contract, the model invents the missing behavior.
With the contract, it has a fixed standard:
- total must never be negative
- these exact inputs must produce these exact outputs
- verification must prove it
That changes the job from “build something plausible” to “build something that satisfies defined behavior.”
The simpler workflow
For a small feature, I do not need a long multi-stage process.
I use a simple three-step loop.
Step 1: Implement
Read FEATURE_CONTRACT.md.
Implement the feature exactly as specified.
Do not add behavior that is not described.
Stop when implementation is complete.
Step 2: Test
Write tests for every scenario in FEATURE_CONTRACT.md.
Assert exact expected values.
Do not change the implementation.
Report any failing test.
Step 3: Verify
Check the implementation against FEATURE_CONTRACT.md.
Confirm the hard rule is enforced.
Confirm all scenarios pass.
Report PASS or FAIL with file and line evidence.
That is usually enough for a smaller feature.
It keeps implementation, testing, and verification separate without turning the whole thing into a ceremony.
What this catches that vague prompting misses
Using the tiny cart example, here is what the contract catches immediately:
Wrong implementation
$total = $subtotal - $discount;
Correct implementation
$total = max($subtotal - $discount, 0);
The difference shows up the moment scenario 003 runs:
subtotal=1000, discount=1500- expected
total=0 - actual
total=-500
Without the contract, the AI might never test that case.
With the contract, it has to.
That is the point.
What I like about this approach
I like it because it is small, and it works on my workflow.
It does not require a huge spec. It does not require heavyweight process. It does not slow simple work down much.
It just forces the missing logic into the open before code exists.
That is usually where the real bugs are hiding.
For tiny features, I keep the contract tiny. For bigger features, I expand it.
Same principle. Different depth.
When I use the lightweight version
I use this smaller format when the feature is not large, but still has one or two rules that must not be guessed.
Good examples:
- clamping totals to zero
- idempotency cases
- preventing duplicate submissions
- enforcing date boundaries
- preserving sort order
- blocking updates in specific statuses
- applying a discount only to eligible items
These are not massive systems problems.
They are just places where the AI’s default assumption can be wrong enough to matter.
The real benefit
The biggest benefit is not better prompts.
There are fewer invisible mistakes.
That is what I care about.
If I can spend ten minutes writing a tiny contract and avoid a cleanly coded, confidently tested bug, that is a good trade every time.
For small features, the contract can be short.
It still does the job.
Final thought
I still use user stories.
They are useful for product conversations.
I just do not treat them as enough for AI implementation.
When the feature has logic, even small logic, I want the AI working against a clear behavioral contract, not filling in the blanks from whatever pattern it has seen most often.
That is the shift.
Not bigger prompts. Not smarter tools. Clearer constraints.
That is what makes AI output safer to trust.
This is the lighter version of the process I use in my own projects. For small features, I want the contract to be just strong enough to remove ambiguity and just small enough to stay practical.
If this approach resonates with you, let’s connect. You can find me on LinkedIn or contact me through my site. I am always happy to exchange ideas with other developers and teams figuring out how to use AI in a way that is practical, disciplined, and production-safe.