Guides
Grep, your language server, or LLMGRAPH?
When a coding agent should use text search, a language server, a type checker or LLMGRAPH, and where each one is blind.
Use text search for strings and config, and your language server for one exact symbol. Use LLMGRAPH when the question spans files (who calls this, what breaks) and you need to know which lines are proven.
All three are useful. They answer different questions, and a good agent picks per question. If the name you're looking for is unique, grep finds the same callers for less.
grep (or ripgrep): best for unique names
A text search is fast, free and exact for exact text. If a function name appears in one place in your codebase, every match is a real hit, and nothing is cheaper.
In our own benchmark on the requests library, for names that appear only once:
| Symbol | True callers | ripgrep: found, tokens read | LLMGRAPH: found, tokens read (with expansions) |
|---|---|---|---|
rebuild_proxies | 3 | 3, 76 | 3, 310 |
get_netrc_auth | 5 | 5, 115 | 5, 297 |
Same callers, fewer tokens. Use grep there.
Where it struggles: text isn't calls. When a name also appears in unrelated code, the search returns every line that contains it, and the agent has to read them all to guess which matter. It also misses calls that don't spell the name, such as a function passed around as a value, and it never says "that's all of them".
Your agent's language server: best for definitions and type errors
A language server (the same engine behind your editor's "go to definition") knows types. Ask it where something is defined and it jumps there. After an edit it reports type errors, including a missing argument in a file the agent never opened, when the call's types are known.
Where it struggles: a language server's find-references returns a list of locations, and the list does not say whether it is everything. Calls on values whose type it does not know can be missing, and the agent has to choose to ask.
LLMGRAPH: best when names collide, before and after an edit
LLMGRAPH answers five questions; four of them matter around a change: who calls this, what breaks if it changes, which callers an edit missed, and what it can't see. Every answer line carries a label for how far it's proven: certain, exactly_these, within_these, maybe_more or cannot_see. The labels are explained in the glossary.
When names collide, it separates look-alikes instead of mixing them. zod (v3.23.8) ships two copies of addIssueToContext; a text search returns 143 lines from both. LLMGRAPH returns:
llmgraph · who_calls addIssueToContext (src/helpers/parseUtil.ts:72): 71 in a set
⚠ type checker starting: answers use our own reading for now; 71 calls unchecked
◇ within_these 71 calls -> one of 1 (closed: repo only) [c] w1
⚠ plus 7 dynamic calls nearby that could reach anything w2
– ruled out: 71 same-name calls (proven: other target) [c] w3
assumes [c]: our own reading, not confirmed by the checker
(lines trimmed)The second line says the type checker had not confirmed yet; the answer says so.
On nine names that also appear in unrelated code, a text search returned hundreds of wrong lines, LLMGRAPH named no wrong callers, and the agent read fewer than half as many tokens. The full numbers are on real answers.
After an edit, its Claude Code plugin holds the agent once if calls are certain to break, and names them by file and line, so the agent can't finish without seeing them (the stop check).
Where it struggles: unique names (use grep), cold starts (the first answer can take a moment), and today it runs on Windows x64 for Python and TypeScript/JavaScript only.
A simple rule for your agent
| Question | Use |
|---|---|
| Where is this defined? | Language server |
| Did my edit introduce a type error? | Language server |
| Who calls this unique name? | grep |
| Who calls this name that appears in several places? | LLMGRAPH |
| What breaks if I change this? | LLMGRAPH |
| Did my edit miss a caller? | LLMGRAPH |
| What can't the tools see here? | LLMGRAPH |
You can put this table in your agent's instructions file. The question for callers has its own page, and the limits of every reader of code are in what static analysis can't see.
See real answers, unedited · Windows x64; Python and TypeScript/JavaScript.