The Problem
You ask AI to add a new API endpoint. Your codebase has 50 endpoints — one uses classes, another functions, another decorators with middleware, another a custom framework wrapper. The agent hesitates, generates something that sorta works but matches none of the existing patterns, and you spend 20 minutes adapting its output to fit your inconsistent architecture.
Inconsistent patterns confuse AI. Consistent patterns make AI confident and correct. AI models are pattern matchers — when patterns repeat at every scale, the agent recognizes them instantly and applies them correctly with minimal context. This guide shows you what self-similar code looks like at function / module / package / service scale, how to test whether your patterns are consistent enough for AI to extend, and the four failure modes (clever code, paradigm mixing, naming drift, partial migrations) that quietly break that pattern-matching.
The Core Insight
Consistent self-similar patterns mean the same pattern repeats at function, file, module, and service levels. AI learns once, applies everywhere.
Think of how a small piece of a tree branch echoes the whole tree. Same shape, different scale. In code: a function should have the same structure as a module. A module should have the same structure as a service.
When AI sees this self-similarity, it builds a mental model fast and transfers it across scales.
The skill that does this
There's a skill that reviews these patterns for you — vc-pattern-consistency-audit. Install it and it counts the rival shapes, names the outliers, then tests whether a fresh agent can infer the winner with no instruction file. Do it by hand once first. The loop has two judgment calls a machine cannot own: which repeated job is worth standardising, and which example you would be happy to own five copies of.
The Walkthrough
Example: Non-Pattern-Consistent Codebase
Each endpoint in your API has different structure:
# Endpoint 1: Class-based
class UserEndpoint:
def get(self, request):
# logic here
pass
# Endpoint 2: Function-based
@app.route('/posts')
def get_posts(request):
# different structure
pass
# Endpoint 3: Decorator-heavy
@authenticate
@rate_limit(100)
@cache(60)
async def get_comments(user_id, post_id):
# yet another pattern
pass
AI prompt: "Add an endpoint for /orders"
AI response: Confused. Generates something that doesn't match any pattern. You have to manually adapt it.
Example: Pattern-Consistent Codebase
Every endpoint follows the same pattern:
# Pattern applied everywhere
@app.endpoint('/users', methods=['GET'])
@require_auth
def get_users(ctx: RequestContext) -> Response:
"""Get all users."""
users = ctx.db.query(User).all()
return ctx.respond(users)
@app.endpoint('/posts', methods=['GET'])
@require_auth
def get_posts(ctx: RequestContext) -> Response:
"""Get all posts."""
posts = ctx.db.query(Post).all()
return ctx.respond(posts)
@app.endpoint('/comments', methods=['GET'])
@require_auth
def get_comments(ctx: RequestContext) -> Response:
"""Get all comments."""
comments = ctx.db.query(Comment).all()
return ctx.respond(comments)
AI prompt: "Add an endpoint for /orders"
# AI response (correct on first try)
@app.endpoint('/orders', methods=['GET'])
@require_auth
def get_orders(ctx: RequestContext) -> Response:
"""Get all orders."""
orders = ctx.db.query(Order).all()
return ctx.respond(orders)
AI recognized the pattern from seeing just 2-3 examples. No extensive explanation needed.
The Pattern Recognition Advantage
AI trained on millions of codebases. When your code follows a consistent pattern, AI matches it to similar patterns in training data. Inconsistent code forces AI to guess what you want.
Patterns at Different Scales
The same pattern should repeat at every level:
| Scale | Pattern Element | Example |
|---|---|---|
| Function | Input → Process → Output | def get_user(user_id): validate() → fetch() → format() |
| Module | Types → Logic → Interface | models.py → services.py → routes.py |
| Package | Domain separation | users/ payments/ orders/ all structured identically |
| Service | API → Business Logic → Data | Every microservice has same layers |
When AI sees a pattern at one level, it knows it applies to all levels.
Real Examples
Consistent Error Handling
# Function level
def get_user(user_id: str) -> Result[User]:
try:
user = db.fetch_user(user_id)
return Ok(user)
except NotFoundError as e:
return Err(e)
# Module level
class UserService:
def get_user(self, user_id: str) -> Result[User]:
try:
return self.repository.get(user_id)
except Exception as e:
return Err(e)
# API level
@app.endpoint('/users/{id}')
def get_user_endpoint(ctx: Context) -> Response:
result = ctx.services.users.get_user(ctx.params.id)
return result.fold(
ok=lambda user: ctx.respond(user, 200),
err=lambda error: ctx.respond(error, error.status_code)
)
Same pattern everywhere: Result[T] type, fold() for handling outcomes. AI learns this once, applies it everywhere.
Consistent File Structure
# Every domain follows same structure
users/
├── models.py # Data definitions
├── services.py # Business logic
├── routes.py # API endpoints
├── tests.py # Tests
└── __init__.py
payments/
├── models.py # Same structure
├── services.py
├── routes.py
├── tests.py
└── __init__.py
orders/
├── models.py # Identical pattern
├── services.py
├── routes.py
├── tests.py
└── __init__.py
AI prompt: "Add a new 'notifications' domain"
AI knows exactly what files to create and how to structure them.
Consistent Dependency Injection
Each service here is handed its database and cache from outside instead of creating them itself — a technique called dependency injection. It keeps every service wired the same way, and lets you swap in a stand-in for testing without touching the service.
# Every service has same DI pattern
class UserService:
def __init__(self, db: Database, cache: Cache):
self.db = db
self.cache = cache
class PaymentService:
def __init__(self, db: Database, cache: Cache):
self.db = db
self.cache = cache
class OrderService:
def __init__(self, db: Database, cache: Cache):
self.db = db
self.cache = cache
AI sees this pattern and knows: "All services take db and cache in constructor." When you ask it to add a new service, it follows the pattern automatically.
Why This Works: AI's Training Bias
AI models are trained on billions of lines of code. Most high-quality code follows patterns:
- Rails apps all follow "convention over configuration"
- React projects have similar component structures
- Django apps use models → views → templates
When your code follows a well-known pattern OR establishes a clear pattern of your own, AI maps it to similar patterns in training data and predicts correctly.
When your code is inconsistent, AI has no anchor. It guesses based on aggregate statistics, which means mediocre generic code.
Failure Patterns
1. Clever Code That Breaks Patterns
Symptom: You used a cool trick in one place. AI can't replicate it elsewhere.
# Clever but inconsistent
@magic_decorator_that_does_everything
def special_endpoint(x, y, **kwargs):
# Metaprogramming magic
return eval(f"process_{kwargs['type']}(x, y)")
# AI can't understand this pattern to apply elsewhere
Fix: Prefer boring, consistent patterns over clever one-offs.
2. Mixing Paradigms
Symptom: Some code bundles data and behavior together into classes — a style known as OOP — some is functional, some is procedural.
Fix: Pick one paradigm for each layer and stick to it. OOP for services, functional for utilities, etc.
3. Inconsistent Naming
Symptom: getUser() vs fetchUserData() vs retrieveUserInfo() for the same operation.
# Bad: Inconsistent names
getUser()
fetchPost()
retrieveComment()
loadOrder()
# Good: Consistent names
get_user()
get_post()
get_comment()
get_order()
Fix: Establish naming conventions and enforce them. Use linters.
4. Pattern Drift Over Time
Symptom: Old code follows pattern A, new code follows pattern B. Codebase is half-migrated.
Fix: When introducing new patterns, migrate old code. Don't leave both patterns coexisting.
When Patterns Become Dogma
Don't force consistent patterns where they don't fit. Some code is genuinely unique and needs custom structure. The goal is 80%+ consistency, not 100%.
Measuring Pattern Consistency
The AI Test
Give AI 3 examples of a pattern and ask it to create a 4th. If it gets it right, your pattern is consistent.
Prompt:
"Here are three API endpoints:
[paste 3 endpoints]
Create a new endpoint for /products following the same pattern."
If AI nails it → consistent pattern ✓
If AI creates something different → pattern is unclear ✗
The Extraction Test
Can you copy-paste a component to a new file/project and have it work with minimal changes?
If yes → consistent (self-contained, follows pattern)
If no → coupled to specific context, not consistent
The Onboarding Test
Show a new developer 2 files. Can they create a 3rd file following the same pattern without guidance?
If yes → pattern is learnable and consistent
If no → pattern too complex or inconsistent
Patterns Tell the Agent How. Seams Tell It Where Not To.
Consistent patterns are how you teach an agent to write new code that matches your codebase. The complement is the seam — a labelled boundary (a marker comment like // CRITICAL: over a payment processor or core auth module) that tells the agent "rewrite around this, never through it." Same engineering instinct, different signal: one invites extension, the other forbids it. Together they cover both directions of every change.
Quick Reference
Consistent Patterns:
- Consistent patterns: Same structure at every scale
- Predictable naming:
get_X()always returns X - Uniform error handling: Every layer handles errors the same way
- Repeatable file structure: New modules look like existing modules
- Standard DI: Dependencies injected the same way everywhere
Pattern Recognition Checklist:
- Can AI infer the pattern from 3 examples?
- Does the same pattern apply at function/module/service level?
- Can new developers learn the pattern in <30 minutes?
- Are exceptions to the pattern explicitly marked?
Consistent vs Inconsistent:
| Inconsistent | Consistent |
|---|---|
| Every file has unique structure | All files in a category match template |
| Mix of classes, functions, decorators | Consistent use of one pattern per layer |
| Custom logic in every component | Shared abstractions across components |
| AI needs full context to understand | AI infers pattern from 2-3 examples |
Key Takeaways
- AI is a pattern matcher. The same shape at every scale (function → module → package → service) lets it generalize from 2-3 examples.
- The AI Test: give the agent 3 examples of a pattern and ask for a 4th. If it nails it, your pattern is consistent enough.
- Aim for 80% consistency, not 100%. Some code is genuinely unique. Force uniformity only where it earns its keep.
- Boring beats clever. Magic decorators, metaprogramming, and one-off paradigms break the pattern the agent uses to extend your code.
- Migrate, don't accrete. Don't leave pattern-v1 and pattern-v2 coexisting; pick one and finish the move.
Related Guides
- Refactoring for AI Comprehension — the inverse move: how to retrofit consistent patterns into a legacy codebase using strangler-fig refactors and seam markers.
- Once your patterns are consistent, anchor the agent on the right example by mentioning that file directly in your prompt, and no more than that.