How I Stop LLMs Drifting In Production Codebases - Scott Spence
Skip Navigation Scott Spence<br>🐱 Choose a theme: Select a themeacidaquaautumnblackbumblebeebusinesscmykcoffeecorporatecupcakecyberpunkdarkdimdraculaemeraldfantasygardenhalloweenlemonadelightlofiluxurynightnordpastelretrosunsetsynthwavetealixvalentinewinterwireframe
Posts Tags Speaking Contact
How I Stop LLMs Drifting In Production Codebases<br>June 21, 2026 • 18 min read<br>pimy-pisvelteguidenotes
Hey! Thanks for stopping by! Looks like this post was updated 7 days ago. Just bear in mind it was originally posted 1 month ago. If there's anything in here which doesn't make sense, please get in touch.
The problem I care about with LLM coding is drift: one plausible<br>shortcut gets copied, then future sessions start treating it as how<br>the app works. I’ve used AI coding tools every day as a team lead on<br>large private client codebases, across features, bug fixes, refactors,<br>reviews, and more recently a finance workflow processing more than<br>$20m a month in capital. When the app has audit trails, permissions,<br>calculations, generated documents, client workflows, and operational<br>handoffs, “prompt harder” is not an engineering system. What works for<br>me is guardrails in the repo: checks, docs, lint rules, tool-call<br>blockers, and handoff validation that stop the model drifting before<br>the mistake becomes normal.<br>That starts with establishing good patterns first. LLMs are pattern<br>followers before they are engineers. They look for the nearest example<br>that appears to work, then continue from there. If the repo is full of<br>clean service boundaries, narrow components, cited docs, targeted<br>invalidation, and boring validation commands, the model has something<br>useful to copy. If the repo is full of shortcuts, the model will copy<br>those too, just faster.<br>For me, drift looks like this:<br>one lazy $effect turns into three more lazy $effects<br>a route imports the database directly because it saw another route<br>do something similar<br>broad cache invalidation appears because it is easier than<br>understanding the dependency graph<br>demo data, placeholder IDs, and temporary glue quietly become<br>production-shaped behaviour<br>the next session treats all of that as precedent<br>This post is the practical version of that system. I’ll cover the<br>layers I use, why they exist, where they should be strict, where they<br>should start as warnings, and a few examples toward the end that you<br>can steal without building my exact setup.<br>Prompting harder is not the solution<br>For a long time the only thing stopping the model from making a mess<br>was me typing “please follow the architecture” again. That is not a<br>workflow. That is me supervising a process, and I have already done<br>enough of that.<br>LLMs are very good at local optimisation. They look at the current<br>file, the nearest import, the most obvious pattern, and the shape of<br>the request. Then they move fast. That is useful when the local<br>pattern is good, and dog shite when the local pattern is temporary,<br>legacy, copied from a spike, or just wrong.<br>So I moved the guardrails to places the model cannot politely forget:<br>the tool boundary, before code is written<br>lint rules, while the diff is still small<br>architecture checks, before bad imports become normal<br>docs retrieval, before the model invents business rules<br>validation scripts, before a handoff says “done”<br>That is the difference between advice and pressure. Advice says “try<br>not to use $effect”. Pressure says “this .svelte file was not<br>modified because the write contained $effect; rewrite it with $derived, an event handler, an action, or a lifecycle API”. One of<br>those scales, the other becomes review debt.<br>The receipts<br>I queried my Pi session history for one private production codebase<br>where I’ve been deliberately building this system.<br>Across 350 sessions:<br>boundary checks appeared in 199 sessions<br>boundary checks failed in 39 sessions<br>boundary checks passed in 65 sessions after fixes<br>LSP diagnostics appeared in 224 sessions<br>Svelte checks appeared in 208 sessions<br>local docs retrieval appeared in 271 sessions<br>project skill files appeared in 278 sessions<br>Those numbers are not a benchmark. They’re just receipts that the<br>rails were in the normal path.<br>The useful bit is that the failures were real. The checks caught<br>things like:<br>route files importing database access directly instead of calling a<br>server service or command<br>browser-side route code mutating domain objects directly<br>route files generating business identifiers locally instead of using<br>server commands or database identifiers<br>demo data and placeholder identities leaking into production-shaped<br>paths<br>remote function files putting schemas and handlers in the wrong<br>place<br>route components and state modules getting too large and mixed<br>concern<br>broad cache invalidation instead of targeted invalidation<br>file naming and identifier naming drift<br>unexplained Svelte effects<br>That is what I want from AI-assisted development. Not a model that<br>never makes mistakes, because that does...