When not to use AI: choosing the tool for the job
Knowing how to use a model is table stakes. Knowing when a model is the wrong tool is what keeps a codebase honest. A short field guide to the cases where we reach for something else.
There is a version of "using AI well" that is really just using AI everywhere. We do not believe in it. A language model is one tool on the bench, and like any tool it has a shape. Good engineering is matching the shape of the tool to the shape of the problem. Here are the cases where we deliberately leave the model out.
When the problem has a deterministic answer
If a task can be solved by a script, a query, a formatter, or a compiler, use that. A model asked to reformat a thousand records will get most of them right. A script will get all of them right, run in a second, and produce the same output tomorrow. Determinism is a feature. Do not trade it away for convenience.
The same applies to code transformations. Structured refactoring tools, codemods, and the language server know the syntax tree. The model is guessing at it.
When you cannot evaluate the output
Using a model to produce something you are not able to judge is not delegation. It is abdication. If you do not know the domain well enough to spot a wrong answer, the model's confidence will fill the gap, and you will ship the mistake with your name on it.
This is our strongest rule. We use AI in areas where we could do the work ourselves, slower. That is what lets us catch it when it is wrong, and it is wrong often enough to matter.
When the cost of a subtle error is high
Cryptography. Concurrency primitives. Financial rounding. Anything safety related. These are areas where an implementation can look correct, pass the obvious tests, and still be wrong in a way that surfaces only under load, in production, on a Friday.
A model is a plausible text generator. Plausible is exactly the wrong property here. For this class of problem we want a reference implementation, a proof, a well reviewed library, or a specification, and a human who has read all of it.
When the task is understanding, not producing
Models are useful for explaining unfamiliar code. They are a poor substitute for reading it. If you are doing impact analysis before a change, the model can give you a map. It cannot give you the understanding that comes from tracing the path yourself, and that understanding is the thing the change depends on.
Read the code. Then, if it helps, ask the model to confirm what you think you saw.
When the context is the whole point
Some decisions are shaped by history: why the team chose this database, why that module is structured strangely, why a certain check exists that looks redundant. The model does not have that history unless someone wrote it down, and most of it was never written down.
For decisions that depend on context, talk to the people who have it. Then write it down, so next time the model has a chance.
When speed would hide the risk
The productive feeling of generating a lot of code quickly is real, and it is dangerous. Volume is not progress. If a feature is arriving faster than the team can review it, test it, and understand it, the tool has become a liability. The right response is to slow the generation down to the speed of comprehension, not to speed up the review to the pace of generation.
What this leaves
Plenty. Drafting boilerplate against an existing pattern. Proposing test cases for a human to triage. Summarizing a long thread. Suggesting names. Producing a first pass at documentation that an engineer then corrects. Acting as a second reader on a pull request. In every one of these, the model proposes and a person decides, and the cost of a wrong proposal is a minute of a reviewer's time.
That is the shape of problem the tool fits. Matching it is the job.