VibeOnCode β€Ί Guides β€Ί 5 Prompt Patterns That Keep AI Code on Rails

5 Prompt Patterns That Keep AI Code on Rails

2026-06-24 Β· 6 min read Β· Prompting
Some tool links may be affiliate links, always labeled.

Five patterns. That's what's left after a year of throwing prompts at AI coding tools and keeping notes on what actually prevented rework. Not fifty tips, not a prompt "library" β€” five structural patterns that address the five ways generated code goes wrong: it solves the wrong problem, it ignores your environment, it can't be verified, it rewrites things you didn't ask about, and it misunderstands you silently.

Everything here is tool-agnostic. The models change monthly as of this writing; these patterns haven't changed all year, because they target the failure modes rather than the tools.

1. Spec-first: make the model meet the problem before the solution

The most common vibe-coding mistake is prompting with a solution sketch ("add a Redis cache to the user endpoint") when you haven't actually pinned down the problem ("p95 latency on profile loads is 900ms"). The model will happily build the wrong solution beautifully.

Spec-first flips the order: describe behavior, inputs, outputs, and edge cases β€” then let implementation follow. The discipline of writing the spec is itself the value; half the time you'll catch your own fuzzy thinking before the model can amplify it.

Build: rate limiting for our public API.

Behavior spec:
- 100 requests/minute per API key, sliding window
- Excess requests get 429 with a Retry-After header
- Limits configurable per key tier (free/pro) from the plans table
- Internal service keys (prefix "svc_") are exempt
- Must fail OPEN if the rate-limit store is unreachable β€” availability
  over strictness for this product

Edge cases to handle: missing key (401, not 429), clock skew between
instances, a key upgraded mid-window.

Don't write code yet. First: list any spec gaps or contradictions
you see, then propose the approach in 5 lines or less.

Note the last paragraph β€” spec-first works best when the first response is a critique, not code. It's cheaper to fix the spec than the pull request.

2. Constraint blocks: your environment, in writing, every time

Models default to their favorite stack, not yours. Left unconstrained, they'll import a new HTTP client into a codebase that has one, invent a config pattern, or "helpfully" upgrade a dependency. A constraint block is a standing, copy-pasteable paragraph that pins the environment:

Constraints (do not violate any):
- Python 3.12, FastAPI, SQLAlchemy 2.x async β€” no new dependencies
  without asking first
- Follow existing patterns: repositories in app/repos, schemas in
  app/schemas, no business logic in route handlers
- All DB access through the existing session dependency, never a
  raw engine
- Error responses use our ApiError class β€” never raise bare HTTPException
- No TODO comments; if something is out of scope, say so in your
  summary instead

Keep this block in a notes file and paste it into every session. It feels bureaucratic exactly once β€” then it becomes the cheapest guardrail you own. Teams I know keep a shared constraint block in the repo itself, which has the nice side effect of documenting conventions for humans too. This is one of the load-bearing pieces of the guardrail system in Vibe Coding With Guardrails.

3. Test-first: the executable spec

The pattern with the best return on effort, full stop. Before any implementation, have the model turn your description into failing tests β€” then read the tests, not the code.

We're adding a coupon system. Do NOT implement it yet.

Write pytest tests for this behavior:
- apply_coupon(order, code) reduces order total by the coupon amount
- Percentage coupons round to the nearest cent, half-up
- Expired coupons raise CouponExpired
- A coupon can be used once per customer, ever
- Stacking is not allowed; applying a second coupon raises
  CouponConflict
- A coupon cannot reduce the total below $0.50 (card minimum)

Make the tests specific enough that a wrong implementation fails.
I'll review the tests before you write any implementation.

Why reading tests beats reading implementations: tests are short, declarative, and written in the vocabulary of your problem. When one says assert total == Decimal("0.50") and you meant free orders to be allowed, you catch the misunderstanding in ten seconds β€” before it's woven through five files. Then "make these pass" becomes the implementation prompt, and the model's grade is objective.

4. Refactor-only: change the shape, freeze the behavior

Ask a model to "clean up" a file without boundaries and it will improve your code the way an enthusiastic house guest improves your kitchen β€” everything is shinier and nothing is where it was. The refactor-only pattern draws the box:

Refactor-only task. The rule: behavior must be IDENTICAL after
this change. No new features, no bug fixes, no dependency changes,
no renamed public functions.

Scope: src/billing/invoice.py only. Do not edit any other file.

Goal: extract the 120-line generate_invoice() into smaller private
functions; replace the nested if-chains with early returns.

If you find an actual bug while refactoring, do NOT fix it β€”
list it at the end under "Found, not fixed."

That last instruction matters more than it looks. Models constantly find real bugs mid-refactor, and silently fixing them sounds helpful but destroys the one guarantee a refactor is supposed to give you: that any behavior change you observe afterward came from somewhere else. Behavior changes and structure changes go in separate commits, always β€” it's the difference between a bisectable history and archaeology.

5. Explain-back: catch the misunderstanding before the code exists

The cheapest pattern on the list, and the one people skip because it feels slow. Before generation, make the model say what it thinks the job is:

Before writing any code: explain back to me
1. what you understand the task to be, in 2-3 sentences
2. your implementation plan as a short numbered list
3. what you are explicitly NOT going to do
4. the riskiest assumption you're making

Wait for my confirmation before implementing.

Something like a third of the time β€” genuinely β€” item 1 or item 4 reveals a mismatch. The model assumed the new endpoint should be public. It planned to store the token in localStorage. It decided "user" meant the admin model. Each of those, caught at explain-back, costs one sentence to correct. Caught in review, it costs a rewrite. Caught in production, it costs a lot more β€” the failure-modes catalog is substantially a list of explain-back steps that never happened.

Choosing the pattern

PatternUse whenFailure mode it kills
Spec-firstNew features, fuzzy requirementsSolving the wrong problem well
Constraint blockEvery session in an existing codebaseStack drift, surprise dependencies
Test-firstAnything with defined behavior, money, or edgesUnverifiable output, silent regressions
Refactor-onlyCleanups, restructuring, pre-feature prepScope creep, buried behavior changes
Explain-backLarge or ambiguous tasks, unfamiliar territorySilent misunderstanding

They compose. A serious feature might use all five: explain-back to align, spec-first to define, a constraint block to pin the environment, test-first to encode the spec, and refactor-only afterward to clean up. That stack sounds heavy and takes maybe ten minutes of overhead on an hour-long task.

My honest take: prompt "engineering" for code is mostly a rebranding of skills the industry already had β€” writing specs, defining scope, demanding verifiability β€” and the people who were good at delegating to junior engineers were good at this on day one. The patterns above aren't clever tricks. They're project management, compressed into text boxes. That's exactly why they'll outlive whatever model you're using this month.

I collect patterns like these β€” plus the full workflows they slot into β€” in the newsletter. Join here and get new ones as they earn their place.

Frequently asked questions

What is the most important prompt pattern for AI coding?

Test-first prompting. Having the model write failing tests from your description β€” and reading those tests before implementation β€” turns vague intent into an executable spec and catches misunderstandings while they're still cheap.

Why does AI-generated code drift from what I asked?

Underspecified prompts force the model to fill gaps with its own defaults β€” popular libraries, generic architectures, scope it assumes you want. Constraint blocks and spec-first prompts close those gaps before generation instead of after.

How do I stop an AI assistant from rewriting code I didn't ask it to touch?

Use the refactor-only pattern: state explicitly that behavior must not change, name the files in scope, and forbid edits elsewhere. Models respect boundaries dramatically better when the boundary is written down.

What is explain-back prompting?

Asking the model to restate the task, its plan, and the risks in its own words before writing any code. Mismatches between your intent and its plan surface in seconds, when correcting them costs nothing.

Do these patterns work with any AI coding tool?

Yes. They're tool-agnostic because they address the underlying failure modes β€” ambiguity, unbounded scope, unverifiable output β€” rather than any specific product's quirks. Syntax may vary; the structure doesn't.


Keep reading

Failure Modes

Where Vibe Coding Breaks: 6 Failure Modes and Their Fixes

Workflow

From Idea to Deployed in a Day: The Full Vibe-Coding Workflow

Workflow

Vibe Coding With Guardrails: Ship Fast Without the Incidents

New patterns, monthly

When a workflow or model change actually matters for how you build, one email explains it.

Monthly. Unsubscribe anytime. Β· Privacy policy