The tool the agent does not call
You can install a tool, document it, and add an explicit instruction telling the agent to prefer it over searching. Then you watch the session and the agent runs a text search anyway. This is the most consistent result I have from building agent tooling, and it took me a long time to stop treating it as a bug.
I will start with my own numbers, because the argument is worthless coming from someone who only ever measured a competitor.
On a large Java repository, I checked how many of the agent’s file reads had been preceded by the command I built specifically to tell it which files to read. 62% had not. The agent opened nearly two files in three without asking the tool that exists to rank them, in a repository where the tool was installed, working, and mentioned in the project instructions.
That’s not a defect report about the model, it’s a fact about how tool use actually happens, and it applies to whatever you are building too.
The first thing I did with that number was look for a bug in my own plumbing, on the theory that an agent cannot bypass an intervention that is genuinely in its path. I found one. It is worth describing before anything else, because it is the failure nearest to hand and the cheapest to rule out, and because ruling it out is what forced the rest of this.
The hook that fired and did nothing
There is a specific engineering error here that I want to describe properly, because I made it and then found the same shape in another project while reading its source, which suggests it is a trap rather than an oversight.
Most agent harnesses (the program actually running the agent loop, like Claude Code or Cursor) let you register a hook that fires before a tool runs. The obvious use is a gentle intervention: when the agent is about to run a search, notice, and point it at the better path first.
So you register the hook on the search tools. The dedicated grep tool, the file glob tool. Reasonable. That is where searching happens.
Then the agent runs a shell command that happens to contain grep -r.
Your hook is registered on the search tools. A shell call is not a search tool. Nothing fires. The agent searches the entire repository, pays for it, and your intervention was never in the path. The other project had the same structure, gated on exactly the tools an agent uses when it is being formal, and blind to the shell it uses when it is being quick.
Mine was slightly worse, and I only found it by reading my own code with this question in mind. The pattern that decides which calls to intercept did include the shell. The handler that runs afterwards only recognised one specific command inside it. So the hook fired on every shell call and then declined to act on almost all of them. It looked wired up. It was matching a broader surface than it could handle, which is the kind of bug that survives review because the tests pass and the logs look busy.
The general lesson is worth stating plainly. Instrument the surface the agent actually uses, not the one your tool taxonomy says it should. A general-purpose shell defeats every category-based gate, because anything can happen inside it.
That bug was real and worth fixing. It was not the explanation. With the intervention properly in the path, the agent still reached for the search it already trusted, which meant the assumption underneath had to be the thing that was wrong: that availability, documentation and an instruction add up to use.
Three things that look like adoption and are not
The mental model I had was a chain. Make the tool available, describe it clearly, and instruct the agent to use it. Each link seemed necessary and the set seemed sufficient. None of the three does what it appears to.
Availability means the tool is in the schema list (the menu of tools the agent is told it can call). The agent knows it exists in the same way you know your kitchen contains a mandoline. Knowing is not reaching.
Documentation is read once, at the start, along with everything else in your instruction files. By turn twenty it is a small and distant part of a conversation that now contains a stack trace, four files, and the user changing their mind. It has not been forgotten exactly. It has been outweighed.
An explicit instruction is the one that feels like it should be decisive. “Prefer this command over grep when locating code.” It is unambiguous, it is in context, and the agent will even agree with it if you ask. It still loses at the moment of choice, because the moment of choice does not feel like a policy decision. It feels like knowing where to look.
What the agent is actually doing
The agent has a habit that works.
Text search is universal, it is understood deeply from training, its failure mode is legible, and it never requires trusting an intermediary. When it returns nothing, that is informative. When it returns too much, the agent knows how to narrow it. There is no interpretation step between the tool and the answer.
A custom tool asks for something extra. It asks the agent to trust a ranking it cannot verify, in a format it has seen far less often, from a source it has no prior about. When your ranked output puts a file first, the agent has to decide whether to believe you. Believing you is a risk. Grepping is not.
The bar people assume is “is my tool better than a text search.” The real bar is “is my tool better by enough, at the exact moment of choosing, to overcome a habit that is already working.” Those are different bars, and only the second one predicts behaviour.
Injection is not adoption either
The next thing you try is stronger. Do not wait to be called. Inject the relevant context into the conversation when the user submits their prompt, so it arrives before the agent decides anything.
This works better and I use it. It is also not a solution to the adoption problem, for a reason that took me a while to see.
Injection only fires on a trigger you can compute in advance, from the user’s words, before any work has happened. That is exactly when you know least. The user says “the export is timing out” and you have to guess what that is about from a sentence with no file names in it. The moment when the agent most needs the right pointer, three turns in, having just discovered the real symbol involved, is a moment when nothing is being injected because nothing new was submitted.
So injection covers the case where the vocabulary happened to match, and misses the case where the vocabulary changed under you. Useful, partial, and not a way to compel use of a tool.
The detector that caught nothing
The last thing I tried before giving up on persuasion was to catch the agent in the act.
The idea seemed sound. My tool surfaces a ranked list of files. Sometimes the right one is on that list and the agent reads three others instead. That is a detectable moment: I know what I surfaced, I can see what got read, so in principle I can notice the gap while the session is still running and say something about it.
I built it and ran it over a corpus of real sessions where I already knew which files a correct answer needed. In 24 cases the agent finished without a file it should have had. The detector caught zero of them. I loosened the threshold until it was firing about seven times per session, and at that setting it caught one. Firing seven times a session to be right once is not detection. That is the base rate expressing itself, and I would have got the same hit count by picking a moment at random.
The reason it cannot work is structural, and I needed the null result to see it. A ranked list of eight files is doing its job when the agent reads one of them. The other seven are supposed to go unread. Counting across the corpus, the agent correctly ignores something like 98% of what gets surfaced to it, which is not a failure mode, it is what ranking is for. So any mechanism that treats an unread surfaced file as a mistake begins wrong on roughly 98% of the cases it inspects, and no threshold repairs a prior that bad. It is unauditable from the other direction too: ask an agent why it skipped a file and you get a fluent, plausible reason, with no way to tell one it actually acted on from one composed on the spot.
So I stopped building anything that questions a read decision. No read-forcing, no asking why a file was skipped, no checklist of what should have been opened. The rule I work by now is four words: surface, don’t steer. The quality of what I put in front of the agent is mine. What it does with it is not, and the ~2% of genuine misses is the price of not owning the other 98%.
What survived that
Three things work, and none of them involve trying harder to persuade the agent.
The first is to lose gracefully. If the agent is going to reach for a text search regardless, the tool should be useful in that world rather than sulking about it. Make the output good enough that when the agent does call it, the call ends the question, so the tool has earned a little more trust the next time the same situation comes up. Adoption is earned per call and it compounds.
The second is to make the output final. Most of the times my tool got called and then the agent grepped anyway, the reason was legible in the transcript: my output raised a question it did not answer. It listed a file without saying whether the file defines the thing or merely mentions it, so the agent opened it to check. That is not the agent being stubborn. That is my output being incomplete. Every follow-up you force is a place where the habit gets to reassert itself.
The third is to say when the answer is nothing. A tool that returns an empty result is nearly useless, because the agent cannot tell “not present” from “your tool failed” and will grep to find out. A tool that says the identifier does not appear anywhere in this repository has actually ended a line of inquiry. Negative results are answers if you state them as answers.
All three sit on the surfacing side of that line. Not one of them asks the agent to be more disciplined, because the detector is what happens when you try that, and it caught nothing.
The rule I carried into coldstart
The design follows from the failure rather than from an ideal.
The commands are shell commands first, because the shell is where the agent already is. Results are ranked with the matched lines shown inline, so the common case is answered without opening anything. An empty result is phrased as a finding about the repository. And notes written after previous work are surfaced automatically at the start of a turn, because the note that has to be requested is the note that never gets read.
Underneath all of it is the same rule, and it is worth separating the two halves. You cannot instruct your way into being chosen, so the only lever left is making the tool cheaper to choose than the habit it competes with, at the exact moment of the choice. And once it has been chosen, the job ends at the quality of what comes back. Surface, don’t steer. Everything above is an application of that; everything I tried that failed was an attempt to argue with the agent instead of out-competing the alternative it already had.
I would not claim this is solved. That 62% is current, not a number from before the fixes, and I would rather publish it than a story where instructions worked. If you are building something in this space, measure the bypass rate before you measure anything else. It is the number that tells you whether you have a tool or a feature nobody reaches for.