Cap at the tool, not the agent
The agent cannot decide to stop reading mid-result. The tool itself must enforce the budget — truncate, rank, summarize. Every fsuite command honors a hard token cap, and reports the cap explicitly when it bites.
A single grep -r ate 180,000 tokens. The agent forgot why it had been called. fsuite started here.
A coding agent — Claude Code, fresh session, clean context — was asked to find every place a particular function was called across a fairly average TypeScript monorepo. The agent reached for its training reflex:
grep -r "doTheThing" .The repo had node_modules. node_modules had a few hundred packages. A handful of those packages had source maps embedded as enormous JSON blobs. Each blob, when grepped, produced one matching line per minified function reference, and the line was the entire minified bundle.
The tool result came back at roughly 180,000 tokens. The agent’s context window was 200,000. Within a single tool call, 90% of the conversation history was now grep noise.
The agent finished the task — sort of. It reported back. The user asked a follow-up question. The agent had no memory of the project structure, no memory of the original goal, no memory of the half-dozen earlier exchanges that had set up the request. It was sitting in 180k tokens of minified JavaScript and 20k tokens of “what was I doing again.”
The problem was not that grep is bad. grep is a beautiful tool for humans, who can pipe it through head, less, awk, or just Ctrl+C when the screen explodes. Agents cannot Ctrl+C. They consume the entire tool result whether they want to or not.
The problem was that the agent’s tooling was designed for humans. Every primitive — Read, Grep, Glob, Bash — assumed unbounded output. None of them had any concept of a token budget. None of them had any concept of “this answer is too large to be useful.”
The first fix was crude: a wrapper script that capped grep output at 2000 lines and reported the truncation. It worked. It also revealed the deeper issue — once you’ve capped the output, you also need to rank the results so the 2000 lines you keep are the useful ones.
Ranking led to ripgrep with relevance scoring. Relevance scoring led to skip-by-default lists for node_modules, dist, .git, vendor. Skip-lists led to a unified search abstraction that could route between filename glob, content match, and symbol lookup based on what the agent actually asked.
That abstraction became fs. The content half became fcontent. The filename half became fsearch.
fsearch(“*.ts”) // scope to TS files first └─ 347 matches · skipped node_modules, dist, .git · fcontent(“doTheThing” | scope: “*.ts”) └─ 12 matches across 9 files · ranked by relevance · fmap(“src/core/handler.ts”) └─ symbol skeleton · 14 functions, 3 classes · next → fread —symbol doTheThing // surgical read
Three principles came out of this incident, and all three are still load-bearing for fsuite today:
Cap at the tool, not the agent
The agent cannot decide to stop reading mid-result. The tool itself must enforce the budget — truncate, rank, summarize. Every fsuite command honors a hard token cap, and reports the cap explicitly when it bites.
Rank what survives the cap
A capped result is only useful if the right matches survived. fsuite’s ranking is path-aware (deprioritize generated/vendor), recency-aware, and exact-match-aware. The first 50 results are the ones an agent would actually want to read.
Skip the obvious noise by default
node_modules, dist, .git, vendor, .venv, __pycache__, target, build — every fsuite tool excludes these unless explicitly told otherwise. The opt-in flag for “no really, search everything” exists, but it is not the default.
Capping fixed flooding. It did not fix reading too much. The agent could now search a repo without burning its context, but the moment it found a match and tried to read the file, all bets were off again. A 600-line file is 600 lines of context whether you needed function 4 or function 24.
That’s Episode 2.
fsearch — the filename search drone born from this incidentfcontent — the content search drone born from this incident