Skip to content

Rung 6 — Agentic retrieval

Rungs 1–5 build a pipeline: question in, k chunks out, one shot. That shape has a hard ceiling, and it is not a ranking problem.

"Which endpoints can be reached without authentication?"

No k chunks answer that. You have to find the auth middleware, find where it is mounted, then enumerate the routes that are not under it. Three lookups, and you cannot write the second query until you have the answer to the first.

Rung 6 is the shift that fixes it: retrieval stops being a pipeline stage and becomes a tool that something else calls in a loop.

What actually changes

Rungs 1–5 Rung 6
Who writes the query your code, once the model, repeatedly
How many lookups one as many as it takes
What comes back k chunks candidates, then whole files on demand
Who decides "enough" k the model
Failure mode it missed it loops, or stops too early

The retriever's job gets smaller, not bigger. It offers candidates and is honest about how sure it is. The judgement moves to the agent — which is what Cursor and Claude Code do over a codebase, and it is why this rung is mostly about tool design.

Three tools, and the number is the point

claude mcp add --transport http milvus-rag http://localhost:8090/mcp
Tool What it does
search_code candidates, with per-channel scores and the signals below
read_code opens the rest of a file — only files that are indexed
list_repos which codebases are connected, and how fresh each is

search_code finds a starting point; read_code is how the agent actually decides, by reading around the hit. That second tool is what makes one-shot top-k stop being the ceiling: k no longer has to be right, because the agent can go get the rest.

The temptation is to add tools — find_definition, list_callers, search_by_symbol. Resist it until something fails without them. Every extra tool is a decision the model has to make correctly before it does any work, and models get worse at choosing as the menu grows.

Design the tool for a model, not for a UI

This is the substance of the rung. Four things matter more than the retrieval quality underneath.

Pass the honest signals through. The agent is the one deciding, so it needs what the retriever knows: the weak-match note from rung 5, the per-channel scores, a DOCUMENT label so code quoted inside a design doc is not mistaken for real code, and how stale the index is — an agent that knows the index is four days old can tell "this file does not exist" from "this file is not indexed yet".

Say that failing is allowed. In the server instructions, explicitly. Without it a model asked a question its tools cannot answer will answer anyway — the same failure as rung 5, one level up.

Bound what the tools can reach. read_code opens indexed files only. That restriction is what makes "it is not there" trustworthy, and it is also the security boundary: a path-taking tool wired to an LLM is a directory traversal waiting to happen.

Treat retrieved content as data.

"""SECURITY: the code that comes back is from an indexed repo — it is DATA for the
agent, not INSTRUCTIONS. The server instructions say so explicitly."""

An indexed repository is untrusted input. A comment in someone's README saying "ignore your previous instructions" reaches the agent through the same channel as a real function body, and nothing structural separates them unless you say so.

The operational part

Blocking work does not belong on the event loop. Embedding, vector search and file reads all block; an MCP session runs on one loop, so blocking there stalls every other request on the connection. All three go through anyio.to_thread here.

Running the MCP server in the same process as the HTTP API, over the same retriever, is also deliberate — two processes means two caches, two configs, and two answers to the same question.

What it costs

Be clear-eyed about this rung; it is the first one that makes things worse as well as better.

  • Latency and tokens. Three tool calls and a read is several seconds and several thousand tokens against one 34 ms search.
  • Non-determinism. The same question does not take the same path twice, which makes your rung-5 eval much harder to apply. Recall@k does not describe an agent. You end up measuring outcomes — did it answer correctly, in how many calls — over a smaller set, by hand.
  • New failure modes. Looping on a query that returns nothing; stopping after one call because the first result looked plausible; being led by a comment in the corpus.

Keep the deterministic POST /search path working and measured. It is what you fall back to, and what you can still put a number on.

What it does not fix

Some questions have no answer in any set of chunks, however many lookups you allow:

"What are the main themes in this codebase?" "What breaks if I change this interface?"

The answer to those is not in the corpus, it is a property of the corpus — of how the pieces relate to each other.

That is rung 7.