I rebuilt the same account scoring engine more times than I want to admit. Not because the client kept changing their mind, and not because the model was wrong. It kept breaking because of how I had arranged the code, which took me a while to see, because from the outside it looked like a data problem every single time.
The setup was ordinary. Several channels feeding one engine: website visitors, email engagement, ad clicks, form fills, content downloads. Each arrived from a different tool, on its own schedule, shaped differently. And each had been built at the moment we needed it, which meant each came with its own little pile of logic attached. This channel filtered out accounts we didn't want. That one applied a score. Another did both, slightly differently, because by then I had learned something and improved it in one place and not the others.
So there was no single scoring function. There were four or five of them, wearing the same name.
What that feels like in practice is not a crash. Nothing errors. You get a list of accounts, it looks like a perfectly normal list, and then someone asks why a particular company is sitting at 72 and you cannot answer without opening three files and reading them side by side. Worse, two channels would disagree about the same company and whichever ran last quietly won. When the sales team stopped trusting the output, they were right to.
I kept fixing it the way you fix a data problem. Adjust a weight here, add a filter there. Every fix worked, and every fix made the next one harder, because I was stacking corrections on a structure that was wrong underneath.
What finally worked was not a better scoring model. It was dividing the work up.
Now every channel gets an ingest function and nothing else. It receives the payload, deduplicates, flags what needs flagging, writes it down raw. No filtering, no scoring, no opinions. One layer down there is exactly one function that scores, reading its points and thresholds from config, and exactly one gate that decides what qualifies. If two sources ever disagree about what "qualified" means, that is now structurally impossible, because neither of them gets to decide.
The engine got easier to change. What I did not expect was how much easier it got to explain, which mattered more, since an engine nobody can explain is an engine nobody acts on.
Granularity is the part worth borrowing
If you are coming into this from marketing rather than engineering, which is where I came from too, borrow the part of technical practice that actually carries weight. It is not the tooling and it is not the vocabulary. It is granularity. Small pieces that each do one job and call other small pieces, instead of long files that do many things at once and can only be understood all at once.
You can watch this get measured. Run a codebase through a quality tool like CodeFactor and long files score badly, almost mechanically. Not because length is a sin, but because a long file is nearly always several jobs sharing one name, and that is exactly what makes it fragile. The score is a proxy for something real: how much you have to hold in your head to change one thing safely.
Where AI actually goes, and why small pieces matter more once it's involved
Most advice about working with AI is about instructing it. Write a better prompt, describe the task more precisely, give it more context. In my experience the real leverage sits on the other side. The instructions that change outcomes are the ones about what the model must never do.
My qualification gate has a set of checks that run no matter what the configuration says. A mission can reorder them, disable the optional ones, tune the thresholds. It cannot switch off the customer check or the anti-ICP check, because those are the ones that cause damage when they are missing, and a configuration file should not be able to cause that damage. There is a similar rule about enrichment: if the data came back empty, the account is rejected and held rather than passed along. An earlier version let those through, and accounts arrived at the sales team looking qualified while carrying nothing at all. The fix was a prohibition, not an improvement.
This is also the second reason to keep things small. A function that does one job gives a model one thing to reason about, inside a boundary you defined. A six-hundred-line file that does five things hands it a novel and asks it to be careful. Atomic pieces are constraints in disguise. You are not only making the code easier for a human to hold, you are shrinking the space in which the model can be creative in ways you did not ask for.
People assume this stops mattering as context windows grow. Maybe eventually. But there is a reason serious systems are still mostly deterministic, and I would put the ratio at something like seventy-thirty. Seventy percent of the system is ordinary automation: rules, thresholds, SQL, things that do the same thing every time and can be reasoned about without running them. Thirty percent is AI, and it belongs at specific crossroads, not spread evenly through the pipeline.
Those crossroads are the places where you would want a person to apply judgment. Is this company actually in our market, given everything we know about it. Is this signal meaningful or noise. Does this message fit what this account is dealing with. In a system that scales, you personally cannot be standing at every one of those points, and that is the honest reason AI belongs there. Not to do the work, but to make the call you would have made, at a volume where you cannot be present. Which means the system prompt at each of those points is not really a prompt. It is you, written down, including the things you would refuse to do.
What I am building next
Splitting things up made the engine coherent. It did not make it self-aware. It still cannot tell me it has quietly stopped working.
So the next piece is a generator that produces synthetic records carrying a marker, fed through the live engine on a schedule. Some are deliberately good, and the engine should accept them and land them in a predictable band. Some are deliberately broken, and the engine should reject them for a specific, named reason. Not "rejected" but "rejected because enrichment came back empty."
That example is not hypothetical, it is the bug I described earlier. A human noticing something odd is what caught it. A marked bad record would have caught it on the first run.
Which is roughly where I think all of this lands. Building stopped being the hard part a while ago. Knowing, on an ordinary Tuesday, whether the thing you built is still doing what you believe it does, has not.