Leverage AI
AI Engineering · Retrieval Design

Grip: A Count Is the Answer to a Bad Search

When a query matches ten thousand records, the honest reply isn't ten thousand rows — it's the number ten thousand, plus the shape of what's underneath. Encode that one reflex and a bad search becomes a one-second lesson instead of a scrolling disaster.

By Scott Farrell, LeverageAI

The short version

Here's a default so universal that nobody thinks to question it: a search function's job is to return the things that match. You give it a pattern, it hands you the rows. That's the contract, and for a well-aimed query it's exactly right.

Now watch it fail. You throw a sloppy pattern at a big corpus — a regex that's a little too loose, a keyword that's a little too common — and it matches ten thousand records. The tool does precisely what it promised: it returns them. All of them, in some summary form, one after another. And in that instant the tool has stopped helping you. A human scrolls a hundred, two hundred, gives up, and never learns the one thing that mattered. An LLM agent does something worse: it dutifully reads the ten thousand into its context and pours the entire window down the drain on noise it can't use — then tries to reason with whatever attention it has left, which is none.

The results were never the answer. On a query that broad, the answer is that the query was bad, and the shape of how it was bad. A good search tool should be able to tell you that — fast, and for almost nothing.

The reflex I was trying to encode

This isn't a new idea. It's a very old human habit that anyone who lives in a terminal already has in their hands, mostly without naming it.

When I'm working a big text file — some sprawling corpus I've never seen the bottom of — I don't start reading. I grip a few things first. I'll pipe the match into wc -l and look at the number: oh, that's ten thousand, that's a rubbish search. I'll count the months to see what time range I'm even standing in. I'll run uniq to see whether the thing is one repeated pattern wearing ten thousand costumes or genuinely ten thousand distinct things. None of that reads a single record. All of it is me interrogating the shape of the result set before I'll pay attention to its contents — because if the shape is wrong, the contents are a waste of my eyes.

That's the reflex. You check the count first because the count tells you whether reading is even worth starting. And the whole idea behind grip was to notice that habit — the way an experienced person actually approaches a large unknown corpus — and build it straight into the tool, so the caller doesn't have to know to do it. Try to encode the human patterns wherever you can find them. This one was sitting right there in wc -l.

You check the count before you read the rows, because the count tells you whether reading is worth starting. Grip is that reflex, encoded into the tool.

The move: flip the return type on breadth

So grip is a small change to the search contract, and it's a change of return type, not just volume.

Under the hood the tool checks how much the query matched before it decides what to send back. Below a threshold — the query is specific, it found a workable handful — you get the normal thing: the rows. Above the threshold — the query blew out, it matched half the archive — the tool refuses to hand you the rows and returns their shape instead: the total count, a histogram over something cheap like the year, and the top handful of a high-cardinality field like sender or source. Not the results. The map of the results.

Notice what that does. I found early on that when you search too broadly, being handed a hundred or two hundred entries in some clipped summary form is almost never what you want. If there were ten thousand answers, the useful fact is that there were ten thousand answers — and where they clump. The count and the histogram aren't a consolation prize for a failed search. They're the actual, load-bearing answer to the question you accidentally asked, which was "how big and what shape is this haystack?"

The 10,904 that proved it

Here's the case that made it concrete. I'd been using an agent to dig through an old email archive — two decades of it — and at one point it threw a deliberately loose pattern at one of the big clusters. The pattern matched almost everything.

The old contract would have shipped the agent ten thousand useless one-liners and ended the session right there — window full, nothing learned. Grip did something else. It didn't hand over the records. It told the agent the number — 10,904 — gave it a histogram of those hits by year, and showed it that the top senders were spam. Three small facts. And from those three facts the next move was obvious: the hits weren't clustered where anything interesting could live, the volume was coming from a handful of junk senders, so the narrowing writes itself — exclude those senders, bound the year range, tighten the pattern, and go again. The bad search had become legible in a single call instead of forty.

Contrast that with the same session's good searches, because the contrast is the whole point. When the agent had an exact token to look for — a specific identifier of the form CCT-then-a-digit — the grep came back with 37 clean hits and pinned the thing in one shot, because that's a known, exact string and determinism is decisive for known strings. And when a different pattern matched nothing, the tool didn't just shrug; a few advisory hints appeared from a similarity search running alongside, pointing at records the exact pattern could never have found. Three different queries, three different failure modes, three different correct responses. The over-broad one didn't deserve rows. It deserved a count.

Why this matters triple for an LLM

For a human, dumping ten thousand rows is rude. For an LLM agent it's fatal, and the reason is arithmetic.

An agent's context window is its attention budget, not a free warehouse — every token you spend on noise is a token that isn't thinking about your problem. So price the two contracts. Ten thousand records, even trimmed to one line each, is tens of thousands of tokens of mostly-junk that crowds out everything the agent actually needed to hold in mind — and once it's in, it's in, quietly degrading every judgment that follows. The grip reply — a number, a little year histogram, five sender names — is a couple of dozen tokens. Same information value about whether the search was any good; a difference of several orders of magnitude in what it costs to find out.

That gap is what makes an agentic search loop affordable enough to iterate. An agent doesn't get the query right on the first try any more than you do; it probes, reads the shape, narrows, probes again. If every over-broad probe cost it the whole window, it could afford exactly one mistake before it was too polluted to think. If an over-broad probe costs it twenty tokens and a clear signal to narrow, it can afford to be wrong a dozen times on the way to being right. Cheap, legible failure is the thing that turns "search" from a single expensive shot into a loop the agent can actually run.

The subtlety: when a count starves, and when it satisfies

There's an honest objection here, and it's worth meeting head-on because it's where the design earns its keep.

A bare count is usually a terrible thing to hand a caller. A lone COUNT(*) is clean, exact, and almost content-free: the agent now knows a cardinality and still can't tell you whether those rows are newsletters, incidents, or gold. Over-compress a result into a single integer and you've starved the caller of the texture any real judgment needs — the same failure, at the opposite end, as blinding it with ten thousand rows. Too much world drowns the pattern; too little world erases it. Useful perception lives in an engineered middle band, and I've written elsewhere about that resolution axis between blinding and starving as its own problem.

So why isn't grip just starvation with extra steps? Because grip isn't answering "what is in here?" — it's answering "was my search any good, and how do I fix it?" For that question, the count plus a histogram plus the top senders is the middle band. The count tells you the search was too broad; the histogram and the facets give you exactly enough texture to see where the volume is coming from and how to cut it. Hand that same reply to someone who genuinely needed the records and you've starved them. Hand it to someone whose query just matched half the archive and you've given them the one thing that moves them forward. Same artifact, opposite verdict — and the tool decides which question is being asked by looking at how much matched. Resolution isn't a fixed property of the response; it's a setting the tool tunes to the breadth of the query.

The third leg of a retrieval trio

Step back and grip slots into place next to two things I've built around it, and the three together cover a space that no one of them covers alone.

Think of retrieval as a rack of sensors rather than a single magic box. Similarity search — RAG — is the needle-in-a-haystack instrument: it's how you find the record whose words you could never have guessed, the one hiding under vocabulary that drifted over twenty years. Exact-token grep is the opposite instrument: deterministic ground truth for a string you already know, the CCT5 you can pin in one shot. And grip — breadth detection, count-and-facets — is the third instrument, the one that answers a question the other two can't even hear: you're asking wrong. RAG did the needle. Grep did the exact token. Facets did the "you're asking wrong." Three failure modes, three answers — and a search surface that's missing any one of them will fail silently in that mode and blame the model.

Grip's neighbours reinforce it. When the exact grep returns nothing, a good system doesn't stay mute; it lets a few advisory hints from the similarity layer surface as a gentle nudge — a pointer, never a pile of chunks, offered with explicit permission to ignore it. That nudge principle is a doctrine of its own and I won't re-litigate it here; the point is that grip and the nudge are the same instinct pointed at opposite failures. The nudge speaks up when nothing matched and you're starved. Grip speaks up when everything matched and you're about to be blinded. Both are the tool feeding the caller information scent — just enough signal to judge which way to move next — instead of making it wander.

A minimal grip layer you could add this week

None of this needs a research budget. Grip is one branch and two aggregates bolted onto a search you already have.

The whole thing, in four decisions

That's it. There's tuning to do — where the threshold sits, which facets earn their place for your data, whether you also return a tiny representative sample alongside the shape — but the mechanism is small, and the payoff is immediate the first time a query blows out and the tool tells you 10,904 instead of trying to show you all of them.


The count is the answer

The line I keep coming back to is that the count is the answer to a bad search, and it's the fastest way to learn your search was bad. The whole trick is refusing the reflex to always return the matches, and asking one prior question first: given how much this matched, does the caller want the rows, or the shape? Get that fork right and a too-broad query stops being a disaster you scroll through and becomes a one-line diagnosis you act on.

If you're wiring search for an LLM agent, this isn't a nicety — it's most of what keeps the loop cheap enough to iterate. Build the wc -l reflex into the tool. Let it hand back the count, the histogram, and the top senders when a query gets greedy. Then the failure is legible in one call instead of forty, and the caller — human or model — walks away knowing not just that it asked wrong, but exactly how to ask again.

Scott Farrell writes on AI engineering, retrieval design, and the economics of building agent systems that iterate cheaply at LeverageAI. If your search tool answers a ten-thousand-hit query by trying to show you all ten thousand, it doesn't have a scale problem — it has a contract problem.