ColGrep-Agent: Cutting Frontier Coding Agents’ Tokens by a Third

Community Article
Published October 12, 2026
hero

Website LinkedIn X

We are releasing ColGrep-Agent-2B, an open 2-billion-parameter code-search agent that runs locally and sits between your coding agent and the repository. Given a question, it searches with ColGrep, our multi-vector semantic code search, reads the promising files, follows references, and hands back file paths, line ranges and the code itself. We plugged it into Claude Code on 52 code-search questions across 13 repositories unseen during training: delegating search to the local agent cut API tokens by 34% and cache-aware API cost by 40% relative to grep, with no statistically significant change in accuracy.

📄 Paper | 💻 GitHub | 🤗 Model | 📦 GGUF

Contents

  1. Introduction
  2. Getting started
  3. What happens when Claude Code delegates its search
  4. How good is a 2B code-search agent?
  5. Training ColGrep-Agent-2B
  6. Small models, big savings

1. Introduction

Before a coding agent can fix a bug, it often has to spend several turns figuring out where to look. It greps for a name, opens a file, follows a reference, tries another search. Each step goes through the frontier model, and the growing conversation gets sent back on the next turn.

We built ColGrep-Agent-2B to handle that part locally. It's a 2-billion-parameter model that searches a repository with ColGrep, reads promising files, follows references, and returns the relevant file paths and line ranges along with the code.

We tested it with Claude Code running Sonnet 5.5 on 52 code-search questions across 13 repositories. Each question was run three times in each of four setups, for 624 sessions total. When Claude Code used ColGrep-Agent as its search tool (colgrep settings --hook-mode agent), it consumed 34% fewer API tokens than grep. These results are for code-search questions, not end-to-end coding tasks.

A coding agent sends a question to ColGrep-Agent-2B, which runs locally, searches with ColGrep over several rounds and returns the locations

Figure 1. Claude Code sends a question to the local 2B agent. The agent searches with ColGrep and returns code locations, so Claude doesn't have to do all the exploration itself.

A quick distinction before we get into it: ColGrep is the semantic code search engine, ColGrep-Agent-2B is the model, and colgrep --agent is the command that runs the model with ColGrep and a read-only terminal.

ColGrep-Agent demo

2. Getting started

2.1. Install ColGrep and the model

brew install lightonai/tap/colgrep && colgrep --install-agent

The second command downloads the default colgrep-agent-2B-Q8_0.gguf model (about 2.5 GB), verifies that it loads, and prepares the prompt cache. There's no Python environment to set up, and no external inference server or API key.

Inference uses the GPU when available (Metal on Apple Silicon, Vulkan on NVIDIA, AMD and Intel GPUs), or falls back to the CPU. On an M3 Pro, a question takes around 20 s with Metal with 3–4 GB of peak memory usage.

Other installation methods

Shell installer (macOS / Linux)

curl --proto '=https' --tlsv1.2 -LsSf https://github.com/lightonai/next-plaid/releases/latest/download/colgrep-installer.sh | sh && "${CARGO_HOME:-$HOME/.cargo}/bin/colgrep" --install-agent

Windows (PowerShell)

irm https://github.com/lightonai/next-plaid/releases/latest/download/colgrep-installer.ps1 | iex
colgrep --install-agent

Cargo (from source)

cargo install colgrep && colgrep --install-agent

2.2. Index a repository

cd /path/to/project
colgrep init

You only need to build the index once. Subsequent searches detect changed files and update the index automatically.

2.3. Ask a question

Here's a session with colgrep 1.8.5 on ColGrep's own source code, running on a Linux machine with an NVIDIA H100. We've left out the agent's reasoning and the individual tool results, and shortened the code of the second location.

$ colgrep --agent "where does the agent decide which GPU backend llama.cpp uses"
◆ colgrep agent · colgrep-agent-2B-Q8_0.gguf · llama.cpp b11476 · NVIDIA H100 80GB
1● colgrepllama.cpp GPU backend 2■ terminalcat -n agent/src/llm/llama.rs 3■ terminalcat -n agent/src/llm/llama.rs | sed -n '80,200p' 4● colgrepdevice backend Metal CUDA Vulkan llama.cpp 5■ terminalcat -n agent/src/llm/server.rs 6■ terminalcat -n agent/src/llm/server.rs | sed -n '85,589p' 7● colgrepfind_gpu 8■ terminalcat -n agent/src/llm/server.rs | sed -n '160,589p' 9◆ finishagent/src/llm/llama.rs:40-57, agent/src/llm/server.rs:65-105
✓ 2 locations · 9 turns · 3 searches · 5 terminal commands · 5.5 s
agent/src/llm/llama.rs:40-57 backend
    33  /// The llama.cpp backend, initialized once per process.
    34  ///
    35  /// On Metal, initialization compiles ggml's GPU kernels from their embedded source.
    36  /// macOS caches compiled shaders (keyed by source and device) in a small shared cache
    37  /// in `$(getconf DARWIN_USER_CACHE_DIR)com.apple.metal`: a hit takes ~50 ms, a miss
    38  /// ~18 s on an M3 Pro. Misses happen on the first run of a new llama.cpp version and
    39  /// whenever other apps' shader compiles have rotated our entry out of the cache.
    40  fn backend() -> Result<&'static LlamaBackend, LlmError> {
    41      static BACKEND: OnceLock<Result<LlamaBackend, String>> = OnceLock::new();
    42      BACKEND
    43          .get_or_init(|| {
    44              // Route llama.cpp/ggml logs to `tracing` (no subscriber: dropped) before the
    45              // backend initializes, so device-init chatter never reaches the terminal.
    46              llama_cpp_2::send_logs_to_tracing(llama_cpp_2::LogOptions::default());
    47              let t = Instant::now();
    48              let backend = LlamaBackend::init().map_err(|e| e.to_string());
    49              BACKEND_READY.store(true, Ordering::Release);
    50              // Printed later by `load`: stderr may be silenced while this runs in the
    51              // background (colgrep mutes it during the encoder load).
    52              BACKEND_INIT_US.store(t.elapsed().as_micros() as u64, Ordering::Release);
    53              backend
    54          })
    55          .as_ref()
    56          .map_err(|e| LlmError::Engine(format!("llama.cpp init: {e}")))
    57  }
agent/src/llm/server.rs:65-105 LlamaServer.start
    65          let (gpu, lib_dir, gpu_note) = if opts.gpu_layers > 0 {
    66              find_gpu(server, opts.progress)
    67          } else {
    68              (None, None, None)
    69          };
        …
    95              .args(["--n-gpu-layers", &opts.gpu_layers.to_string()])
        …
Gold bar: the lines the agent returned. Dimmed lines are the context ColGrep shows with each location.
Plain-text transcript
$ colgrep --agent "where does the agent decide which GPU backend llama.cpp uses"
◆ colgrep agent  · colgrep-agent-2B-Q8_0.gguf (llama.cpp b11476, GPU: NVIDIA H100 80GB HBM3)
❯ where does the agent decide which GPU backend llama.cpp uses
  ● colgrep   llama.cpp GPU backend
  ● terminal  cat -n agent/src/llm/llama.rs
  ● terminal  cat -n agent/src/llm/llama.rs | sed -n '80,200p'
  ● colgrep   device backend Metal CUDA Vulkan llama.cpp
  ● terminal  cat -n agent/src/llm/server.rs
  ● terminal  cat -n agent/src/llm/server.rs | sed -n '85,589p'
  ● colgrep   find_gpu
  ● terminal  cat -n agent/src/llm/server.rs | sed -n '160,589p'
  ● finish    agent/src/llm/llama.rs:40-57, agent/src/llm/server.rs:65-105
✓ 2 locations · 9 turns · 3 searches · 5 terminal commands · 5.5s

agent/src/llm/llama.rs:40-57  backend
    33  /// The llama.cpp backend, initialized once per process.
    34  ///
    35  /// On Metal, initialization compiles ggml's GPU kernels from their embedded source.
    36  /// macOS caches compiled shaders (keyed by source and device) in a small shared cache
    37  /// in `$(getconf DARWIN_USER_CACHE_DIR)com.apple.metal`: a hit takes ~50 ms, a miss
    38  /// ~18 s on an M3 Pro. Misses happen on the first run of a new llama.cpp version and
    39  /// whenever other apps' shader compiles have rotated our entry out of the cache.
    40  fn backend() -> Result<&'static LlamaBackend, LlmError> {
    41      static BACKEND: OnceLock<Result<LlamaBackend, String>> = OnceLock::new();
    42      BACKEND
    43          .get_or_init(|| {
    44              // Route llama.cpp/ggml logs to `tracing` (no subscriber: dropped) before the
    45              // backend initializes, so device-init chatter never reaches the terminal.
    46              llama_cpp_2::send_logs_to_tracing(llama_cpp_2::LogOptions::default());
    47              let t = Instant::now();
    48              let backend = LlamaBackend::init().map_err(|e| e.to_string());
    49              BACKEND_READY.store(true, Ordering::Release);
    50              // Printed later by `load`: stderr may be silenced while this runs in the
    51              // background (colgrep mutes it during the encoder load).
    52              BACKEND_INIT_US.store(t.elapsed().as_micros() as u64, Ordering::Release);
    53              backend
    54          })
    55          .as_ref()
    56          .map_err(|e| LlmError::Engine(format!("llama.cpp init: {e}")))
    57  }

agent/src/llm/server.rs:65-105  LlamaServer.start
    65          let (gpu, lib_dir, gpu_note) = if opts.gpu_layers > 0 {
    66              find_gpu(server, opts.progress)
    67          } else {
    68              (None, None, None)
    69          };
        …
    95              .args(["--n-gpu-layers", &opts.gpu_layers.to_string()])
        …

Each location starts with path/to/file:START-END and the name of the enclosing function, followed by its code, so a coding agent can often answer without opening the file. The format is also easy to feed into scripts. When you run the command in a terminal, progress goes to stderr; stdout is reserved for the results.

A couple more examples

Search within a directory and include the matching code lines:

colgrep --agent "crash when the config file is empty" ./backend -c

Get JSON output:

colgrep --agent --json "where are retries configured"

To see the ColGrep agent in action, explore the additional trajectories on SWE-bench Lite and Multi-SWE-bench Flash in the dedicated space below!

2.4. Connect it to your coding agent

colgrep --install-claude-code   # Claude Code
colgrep --install-codex         # Codex
colgrep --install-opencode      # OpenCode

With the integration installed, Claude Code can pass a question like "Where is this behavior implemented?" to colgrep --agent and get back locations and code instead of working through every search itself.

The Claude Code integration uses hooks that suggest a search tool in the agent's context; they don't force Claude to use it. You can change the suggestion with colgrep settings --hook-mode:

colgrep settings --hook-mode hybrid    # default: Balanced setup in section 3
colgrep settings --hook-mode agent     # suggest ColGrep-Agent: largest token savings
colgrep settings --hook-mode colgrep   # suggest plain ColGrep only

3. What happens when Claude Code delegates its search

3.1. How we measured it

Repositories. We chose 13 public repositories created in 2026 to reduce the chance that the models had seen them during training. We also asked Sonnet 5.5, without repository access, to name 10 source files from each one. It couldn't name any. That's a useful sanity check, though it doesn't prove the repositories were absent from training data. The repositories span Rust, Go, TypeScript and Python, and range from 17k to 1.8M lines of code.

Questions. We built 52 questions, four per repository and ran 3 times each question:

  • 39 cross-folder questions: we find a real reference chain (a symbol defined in one place and used in one or two others) then ask Claude Opus 5.5 to write a debugging question that requires finding all those locations.
  • 13 single-location questions: these describe a behavior without necessarily using the same words as the code.

Setups. We run Claude Code with Sonnet 5.5, keeping the question and repository fixed while changing the available search tools. Each setup gets its own isolated configuration:

Setup How Claude Code searches
Grep Built-in tools only: grep, find and file reads.
ColGrep Claude can call semantic search directly, without a separate agent.
ColGrep-Agent Claude delegates searches to colgrep --agent, which returns code locations.
Balanced Claude can use plain colgrep for names and patterns, or colgrep --agent for behavior questions.

Table 1. The four setups. colgrep --install-claude-code installs the hooks that suggest the available search tools.

We ran each question three times per setup: 624 sessions in total.

What we count. Token totals include Claude Code's API input and output tokens. They don't include tokens generated by the local 2B model, since those aren't billed. We compute 95% bootstrap confidence intervals over questions, averaging the three runs of each question first.

Grading. Claude Opus 5.5 grades the final answers against the reference locations as correct, partial or incorrect, without knowing which setup produced them. For cross-folder questions, an answer is correct only if it finds every required location. A separate judge compares the full traces: for each question and run, it sees the four traces in random order and ranks them by answer correctness, then by how directly they reached the answer.

3.2. A third fewer tokens

Tokens saved by ColGrep and ColGrep-Agent vs Grep

Figure 2. Token savings relative to Grep. Dots show point estimates; bars show 95% bootstrap confidence intervals over questions.

52 questions × 3 runs Grep ColGrep ColGrep-Agent Balanced
Tokens per question 142k 107k 93k 112k
Tokens saved [95% CI] n/a 24.6% [16, 32] 34.3% [28, 40] 21.2% [13, 29]
Questions using fewer tokens n/a 79% 88% 75%
Cost per question (cache-aware) 7.3¢ 6.0¢ 4.4¢ 5.1¢
Correct answers 83.3% 79.5% 80.8% 83.3%
Turns per question 5.5 4.3 3.9 4.3
Time per question 13.0 s 15.0 s 21.6 s 17.7 s

Table 2. Results across 52 questions, each run three times per setup.

With ColGrep-Agent, Claude Code used 34.3% fewer tokens than with Grep: about 49k fewer per question. It used fewer tokens on 88% of the questions, and the 95% confidence interval for savings was 28–40%.

ColGrep-Agent answered 80.8% of questions correctly, versus 83.3% for Grep (−2.6 percentage points; 95% CI −10.9 to +5.1). That difference isn't statistically significant, but the interval is wide enough that we can't rule out a meaningful drop. Balanced version matched Grep's observed 83.3% accuracy while saving 21% of tokens.

Share of comparisons where the judge prefers each setup over Grep

Figure 3. How often the trace judge prefers each setup over Grep.

The trace judge also favored ColGrep-Agent: in head-to-head comparisons with Grep, it ranked the agent's trace higher 75% of the time. A common pattern was one delegated search instead of several rounds of guessing symbol names.

3.3. Example

Here's a concrete example from a CRM repository. These are the first runs from each setup, with commands shortened for readability. Both answers were correct. This particular question saw 51% token savings, so it's a better-than-average case.

If I add a new property to the custom attributes that admins define on records (like contacts or deals), where is the shape of the list returned to the frontend declared, and where does the auto-generated typed API layer bring that shape in to type the endpoint that lists those attributes?

The tricky part is vocabulary: the code calls these things fields (fieldDefinition, fieldListOutput), not "custom attributes." With grep, Claude has to work out the naming first:

Grep 6 turns · 5 tool calls 160.6k tokens
turn 1 · grep
grep "customField\|attributeDefinition"
→ a planning doc
turn 2 · grep
grep "attribute"
→ dozens of unrelated files
turn 3 · grep
grep "propertyDefinition\|fieldDefinition"
→ apps/api/src/fields/
turn 4 · read + grep
cat fields/*.router.ts; grep "field" generated/*.ts
→ fieldListOutput, server.ts
turn 5 · grep
grep -A6 "fieldListOutput" fields.contracts.ts
→ the schema, line 101
turn 6 · answer ✓ correct
fields.contracts.ts (serializedFieldOutput, fieldListOutput)
generated/server.ts (fields router)
ColGrep-Agent 3 turns · 2 tool calls 79.2k tokens
turn 1 · colgrep --agent
"custom attribute definitions list endpoint output schema, and generated typed trpc API layer (AppRouter) that imports that shape"
→ fields.service.ts, with its code
turn 2 · grep
grep "fieldList" server.ts; grep "serializedField"
→ fields.contracts.ts:101, server.ts:26
turn 3 · answer ✓ correct
fields.contracts.ts (fieldListOutput)
generated/server.ts (appRouter)
The agent's first search already lands in the fields module, so Claude only checks the remaining details.
Plain-text version of both traces
Grep: 6 turns, 5 tool calls, 160.6k tokens
  grep "customField\|attributeDefinition"    → a planning doc
  grep "attribute"                           → dozens of unrelated files
  grep "propertyDefinition\|fieldDefinition" → apps/api/src/fields/
  cat fields/*.router.ts; grep "field" generated/*.ts
                                             → fieldListOutput, server.ts
  grep -A6 "fieldListOutput" fields.contracts.ts
                                             → the schema, line 101
  Answer: fields.contracts.ts (serializedFieldOutput, fieldListOutput),
          generated/server.ts (fields router)                 ✓ correct
ColGrep-Agent: 3 turns, 2 tool calls, 79.2k tokens
  colgrep --agent "custom attribute definitions list endpoint output
    schema, and generated typed trpc API layer (AppRouter) that
    imports that shape"                      → fields.service.ts, with its code
  grep "fieldList" server.ts; grep "serializedField"
                                             → fields.contracts.ts:101, server.ts:26
  Answer: fields.contracts.ts (fieldListOutput),
          generated/server.ts (appRouter)                     ✓ correct

ColGrep's multi-vector search can match a description of "custom attributes" to code about fields (see section 5.3). The agent then follows up on the promising results and returns code locations. Claude still checks the answer, but it doesn't have to do all the initial digging.

3.4. Why it saves tokens

Tool calls per question, by tool

Figure 4. Average tool calls per question, by tool.

The main saving comes from cutting down Claude's search turns. Each new turn resends its accumulated context (about 23k tokens per turn in these runs). With Grep, Claude averaged 5.5 turns, 5.1 grep/find/ls commands and 2.0 file reads per question. With ColGrep-Agent, it averaged 3.9 turns, 2.0 grep commands and 0.9 file reads.

The larger the repository, the more search work there tends to be. We measured savings of 19% below 50k lines, 29% from 50k to 150k, and 40% from 150k to 600k. ColGrep-Agent saved tokens on all 13 repositories.

3.5. Time it

Time per question

Figure 5. Wall-clock time per question, compared with Grep's 13.0 s.

The trade-off is latency. On our GPU server, a local ColGrep-Agent call took 7 s at the median and 22 s at the 90th percentile. End-to-end question time rose from 13.0 s with Grep to 21.6 s with ColGrep-Agent.

More results: best trace, turns, repository size and per-repository savings Share of items where each setup has the best trace

Figure 6. Share of questions and runs where each setup had the top-ranked trace.

Turns per question

Figure 7. Average Claude Code turns per question.

Tokens saved by repository size

Figure 8. Token savings by repository size. On the two repositories over a million lines, where Grep averaged nearly nine turns per question, ColGrep and ColGrep-Agent saved 37–46%.

Tokens saved on each repository

Figure 9. Token savings by repository, sorted by lines of code. With four questions per repository, individual estimates are noisy.

The ten largest savings with ColGrep-Agent

Figure 10. The ten questions with the largest token savings under ColGrep-Agent. Six are cross-folder questions.

4. How good is a 2B code-search agent?

4.1. Comparison with larger models

ColGrep-Agent performance versus model size

Figure 11. Average file F1 (left) and line F1 (right) versus model size, across SWE-bench Lite and Multi-SWE-bench Flash with the ColGrep harness.

We also wanted to know how well the 2B model could locate code on its own, rather than just how much it helped Claude Code. We adapted SWE-bench Lite and Multi-SWE-bench Flash into code-localization benchmarks, following the method in our paper.

The task here is not to generate a patch or resolve the issue. The model must identify the files and lines relevant to the bug. That lets us measure search accuracy separately from code generation.

Model Harness SWE-bench Lite Multi-SWE-bench Flash
File F1 Line F1 File F1 Line F1
lightonai/colgrep-agent-2B ColGrep 76.18 19.92 62.57 15.93
google/gemma-4-31B-itColGrep79.9317.0868.3019.14
Qwen/Qwen3.5-27BColGrep77.9315.7168.2816.63
google/gemma-4-26B-A4B-itColGrep76.3513.3463.5014.75
Qwen/Qwen3.8-27BColGrep60.807.7059.6811.50
deepseek-ai/DeepSeek-V4-FlashColGrep74.0411.3664.2613.64
openbmb/MiniCPM5-2BColGrep66.729.6252.0111.31

Table 3. File and line F1 (%) on two code-localization benchmarks, averaged over five seeds.

We evaluate several open-source models using our ColGrep harness. File and line F1 measure overlap with the reference files and lines. Scores are averaged over five seeds (300 instances per benchmark per seed), with strict predictions, a 10-turn limit and mandatory final submission. Comparison models run at temperature 0.6 without reasoning.

ColGrep-Agent-2B delivers competitive repository-localization performance against models more than 10× its size. On SWE-bench Lite, it achieves the highest line F1 (19.92) among all evaluated models, outperforming substantially larger general-purpose models. This strong performance extends across multiple programming languages on Multi-SWE-bench Flash, where it achieves 62.57 file F1 and 15.93 line F1, surpassing several larger models in line localization. These results demonstrate that task-specific training enables compact models to rival much larger general-purpose models across diverse codebases and programming languages.

4.2. Comparison with other code search tools

On the Semble code-search benchmark, ColGrep-Agent-2B returns fewer files than ranked search, and most of them are relevant.

Method NDCG@10 Precision@10 Recall@10 F1 Files returned
ripgrep 0.125 0.037 0.158 0.052 1.2
BM25 0.707 0.296 0.865 0.411 4.6
CodeRankEmbed 0.844 0.132 0.956 0.226 9.2
Semble 0.855 0.141 0.966 0.241 8.5
ColGrep 1.8.5 (ours) 0.850 0.117 0.966 0.207 9.8
ColGrep-Agent-2B (ours) 0.868 0.744 0.887 0.776 1.7

Table 4. Results on the Semble code-search benchmark (1,251 questions, 63 repositories, 19 languages), scored at the file level with the benchmark's code. Precision@10 is the share of returned files that are relevant, over at most 10 files; F1 combines it with recall@10 for each question. Higher is better, except for files returned.

5. Training ColGrep-Agent-2B

We started from MiniCPM5-2B and trained in two stages: a short supervised fine-tuning (SFT) cold start, then reinforcement learning with a variant of Group Relative Policy Optimization (GRPO).

ColGrep-Agent-2B training pipeline

Figure 12. The two training stages, SFT cold start then GRPO, with the reward used in the second stage. The harness and turn budget are identical at training time and in `colgrep --agent`.

5.1. Agent harness

We built a dedicated, minimalist harness for code localization with ColGrep. Given an issue and a repository, the agent searches the code with ColGrep, inspects promising locations and submits file paths with line ranges. The harness exposes just three tools and controls the search loop and turn budget.

The agent searches with ColGrep, our semantic code search tool built on NextPlaid. ColGrep supports natural-language queries alongside regex and path filters. It proposes relevant code; the agent decides which results to inspect and when to search again.

Under the hood, ColGrep uses LateOn-Code-edge, a 17M-parameter multi-vector retrieval model we introduced in an earlier post. Instead of representing a whole function with one embedding, it keeps a vector for each token and compares query tokens against code tokens. You get some of the specificity of lexical retrieval, but with softer matching. For example, a query about "custom attributes" can surface fieldDefinition even though the words differ.

That matters because coding agents describe behavior differently from how a codebase names it. A useful first search can save several rounds of trying possible identifiers. The index is stored locally with NextPlaid and updated incrementally as files change; code doesn't need to leave the machine for retrieval or agent inference.

The agent has three tools:

Tool Purpose
colgrep Semantic code search, with optional regex and path filters.
terminal Read files and inspect the repository with read-only commands.
finish Return locations as path/to/file:START-END.

Table 5. Tools available to ColGrep-Agent-2B.

It can search, open a promising file, follow a reference and search again, rather than betting everything on one query. It has a 10-turn budget, with the harness forcing a finish call on the final turn. The released colgrep --agent command uses the same system prompt, tool schemas, chat template and turn budget as training.

5.2. Training data

We built a multilingual code-localization dataset spanning several programming languages, using subsets of nebius/SWE-rebench and internlm/SWE-Fixer-Train-110K, alongside samples from additional scraped repositories. Each example contains a repository, an issue description, and the commit that fixes it. The agent searches the repository before the patch, while the patch provides reference file and line locations for training. We use these sources in both SFT and RL.

5.3. Training

For the cold start, we generate non-reasoning trajectories in our ColGrep harness using Qwen3.5-27B as the teacher, then filter them and perform SFT on MiniCPM5-2B. During teacher generation, we replace grep calls with corresponding ColGrep commands: ColGrep also supports grep-style lexical search using regex filters alone, without a natural-language query. This lets us retain useful trajectories that would otherwise use grep, while teaching the smaller model to use ColGrep through actual tool calls and results.

We follow this first stage with reinforcement learning using GRPO in the same code-localization harness. Inspired by DAPO, we use zero-advantage filtering to discard trajectories with no learning signal, including groups where every attempt receives the same reward. We also average the loss over trainable tokens across the batch and omit the KL-divergence penalty to the frozen reference model. Each trajectory's advantage is its reward minus the group's mean reward, without dividing by the group's standard deviation, as in Dr. GRPO.

The reward starts from the equal-weight average of file F1 and line F1, both on a 0–1 scale, with line F1 measuring exact file-and-line overlap. We apply a turn penalty after the second assistant turn to discourage unnecessary exploration. We also add a tiny penalty when the model invokes grep, and another penalty if it never searches or fails to submit locations through the finish tool.

We use asynchronous RL with a maximum of 4 off-policy steps, training for 500 steps and sampling 16 trajectories per issue with a training batch size of 128 trajectories. We optimize with AdamW at a constant learning rate of 1e-6, weight decay 0.01 and gradient norm clipping at 1.0. Rollouts use temperature 1.0, a 16,384-token context, and up to 1,024 generated tokens per turn, with reasoning disabled.

6. Small models, big savings

We don't think every step in a coding agent's workflow needs to run through a frontier model. Searching for the right files is a good example: it's important work, but much of it can be handed to a smaller model trained specifically for repository exploration.

That's what we tested with ColGrep-Agent-2B. In our code-search benchmark, giving Claude Code a local search agent reduced its API token usage by 34% and cache-aware API cost by 40%. Searches also took longer. For a lower-latency default, the Balanced setup saved 21% of tokens while matching Grep's observed accuracy.

The model and GGUF weights are available now.

Related resources

Citation

@misc{colgrepagent_2026,
  title        = {ColGrep-Agent},
  author       = {Lasbordes, Maxence and Sourty, Raphael and Sinha, Aarush and Chatelain, Amélie},
  year         = {2026},
  howpublished = {\url{https://huggingface.co/blog/lightonai/colgrep-agent/}}
}

Acknowledgements

ColGrep is developed in the open at lightonai/next-plaid. Thanks to its contributors: Chao-Chun (Joe) Hsu, Robert Wasilewski, Anton Chen, Vlad Lasky, Artem Dinaburg, DeeJayPee, George, Igor Carron, Omer Faruk Oruc, Abhishek Tripathi, Amir, graelo, Gray Olson, Jeff Coggshall, Jonathan Ellis, Mark Motliuk, NickSdot, Paolo Valletta, Raymond Weitekamp and Szymon Rączka.

We also relied on open models, datasets and tools: MiniCPM from OpenBMB, our base model; Qwen, whose Qwen3.5-27B we used as the SFT teacher; SWE-bench, Multi-SWE-bench, SWE-Fixer and SWE-rebench for training and evaluation; TRL, prime-rl and vLLM for training and inference; and llama.cpp, ONNX Runtime, tree-sitter and Hugging Face Tokenizers, which ColGrep uses.

Community

Thanks for publishing the accuracy interval (−10.9 to +5.1) next to the token savings. Most cost write-ups leave that out.

The check I'd add is accuracy split by question type, with partial answers counted on their own. Table 4 shows why. On Semble the agent returns 1.7 files at 0.887 recall@10, against 9.8 files at 0.966 for plain ColGrep. That's a good precision trade. But in delegation mode Claude only sees what the agent hands back, so a file the agent misses is invisible downstream. On the 39 cross-folder questions, where "correct" needs every location, I'd expect that recall gap to show up as more partial answers rather than as tokens.

So two numbers would settle it: the partial rate per setup on cross-folder vs single-location questions, and the token savings measured only on questions where both setups were correct. If the savings hold on that both-correct subset, the cut isn't coming from stopping early. Balanced already matching Grep's observed accuracy at 21% savings hints that routing by question type is where the safe win is.

ColGrep-Agent on a real production codebase: what we measured, a model-loading fix, and why it pairs with a field guide

By Rick Young, founder of SiliconExchange, a US marketplace for buying and selling PC hardware. Written with Claude Code, which ran the tests.

LightOn's ColGrep-Agent post makes a simple promise: put a small code-search model on your own GPU, let it do the grepping and file-skimming, and your frontier coding agent spends fewer tokens finding code. We run Claude Code (Opus) on a private ~178,000-line TypeScript/Next.js codebase every day, and there's an RTX 5090 under the desk, so we tried it the same afternoon.

This post covers what we installed, how we tested it, what we found (including a model-loading problem and its fix), and why it works alongside a tool we already use, Field Guide.

TL;DR

  • Tokens: with ColGrep-Agent added, Claude used 14% fewer tokens and 25% fewer tool calls on 10 real "where is this code?" questions. Accuracy was unchanged: 10 out of 10 in both arms.
  • Where it helps: questions whose answer spans several files saved 33–37%. Single-file questions saved nothing.
  • The 2B model alone is a pointer, not an answer: on its own it fully answered 4 of 10 questions and partly answered 5. The frontier model still has to open the files.
  • Model loading: out of the box, every colgrep --agent call cold-starts its own llama.cpp server and loads the model. Running one persistent llama-server with 4 parallel slots cut a question to 2.5–4.4 s. Ten simultaneous questions finished in 16.5 s, on one shared copy of the model.
  • One required flag: the persistent server must be started with --special, or every call fails.
  • Field Guide + ColGrep: a field guide tells the agent which system owns something and what rules guard it. ColGrep finds the exact lines. They answer different questions, and the savings stacked in our test.

Setup

  • Windows 11, RTX 5090 (32 GB), Claude Code in the Claude desktop app, Opus 5.5.
  • ColGrep v1.8.5 from the official GitHub release. We downloaded the Windows zip directly and checked its SHA-256 instead of piping the installer.
  • colgrep --install-agent pulled colgrep-agent-2B-Q8_0.gguf (~2.5 GB) and a Vulkan build of llama.cpp. It detected the 5090 on the first try.
  • colgrep init indexed 1,017 files in 91 seconds.

Secrets check first. Like most apps, the repo has a git-ignored env file with credentials in it. Before letting any tool return code into an agent's transcript, we read the index's metadata database and diffed it against git ls-files. Every indexed file was git-tracked; nothing ignored, no .env*. Do the same check for your own repo instead of trusting a README line.

We did not install the Claude Code hooks (colgrep --install-claude-code). They install machine-wide, across every project, and we wanted to measure before changing anyone's defaults.

How we tested

We wrote 10 questions about our own codebase, each with an answer key (files and line ranges) checked against the code before any run. They cover the parts of a marketplace where a wrong answer is expensive: payment completion, order sweeps, session revocation, sales-tax display, offer handling, dispute deadlines, URL token scrubbing, listing publish rules, an abuse rate limit and shipping deadlines. They are worded the way a developer would ask, for example "When the last unit of a listing sells, what happens to other buyers' pending offers, and where is that triggered?"

Each question was answered by a fresh Opus subagent, twice:

  • A (our current practice): Grep, Glob, Read, and our field-guide lookup.
  • B: the same tools, plus an instruction to run colgrep --agent "<question>" first and verify from there.

We recorded each subagent's processed tokens, tool calls and wall time, and graded the answers against the keys.

Results

A: field guide + grep B: + ColGrep-Agent
Tokens processed (10 questions) 1,084,750 934,527 (−13.8%)
Tool calls 56 42 (−25%)
Correct 10/10 10/10
Avg wall time per question 16 s 30 s (built-in runtime, see below)
  • The savings concentrated where you'd expect. The two widest questions, one an order sweep spanning a cron route, a sweep library and a refund helper, the other a payment handler that voids offers, dropped from ~130k to ~87k tokens each. Questions that live in one well-named file came out flat, because grep finds those in one step anyway.
  • This is smaller than LightOn's 34%, and we think the difference is real, not noise. Their baseline was plain grep. Ours already had a field guide that sends the agent to the right system in one call, so some of the savings ColGrep offers were already taken.
  • Caveats: 10 questions, one run each, one repo, one model. Token counts are what the subagent processed, including cached context re-read on every turn. That's why fewer turns means fewer tokens even when no file is skipped. Treat 14% as an indication, not a guarantee.

The 2B agent on its own. We also ran colgrep --agent alone on all 10 questions. It took ~5 s each. It fully answered 4 and partly answered 5. Typically it found the constant but not the function that enforces it, or the library but not the caller. One answer included a 2,400-line range covering most of an email module. That's fine, because it isn't the job: in arm B, Opus took the pointer, opened the files, and closed every gap. Use ColGrep-Agent to locate code; don't use it to answer.

The model-loading finding, and the fix

Arm B's wall time was nearly double arm A's. Two causes:

  1. Every colgrep --agent call starts its own llama-server and loads the model, then shuts it down. You pay that cold start on every question: about 10 s on the first call, about 5 s after that.
  2. Parallel calls each load their own copy. We ran 5 subagents at once, which meant 5 servers and 5 copies of the model competing for the GPU. Ten at once would need ~30 GB of VRAM for model copies alone.

The Claude Code hooks don't change this. We read the v1.8.5 source: colgrep --install-claude-code adds a SessionStart hook and two PreToolUse hooks that only inject text, telling Claude (and any subagents it spawns) which search command to prefer. None of them starts or keeps a server. --hook-mode only picks that advice. hybrid, the default, steers most searches to plain colgrep, which uses only the small 17M embedding model, and saves colgrep --agent for questions about behaviour. So hybrid mode makes you pay the 2B model's load cost less often, but it never keeps the model loaded.

The fix is built into ColGrep. It can send its questions to any OpenAI-compatible server through --agent-endpoint. So we run llama.cpp's server once, with the model loaded and 4 parallel slots:

llama-server.exe ^
  -m "%USERPROFILE%\.cache\huggingface\hub\models--lightonai--colgrep-agent-2B-GGUF\snapshots\<hash>\colgrep-agent-2B-Q8_0.gguf" ^
  --host 127.0.0.1 --port 8765 -ngl 999 -c 65536 --parallel 4 --jinja --special
colgrep settings --agent-endpoint http://127.0.0.1:8765/v1

--special is mandatory. Without it llama-server strips special tokens from completions, and those tokens are the agent's tool-call markup. Every call fails with:

removed special tokens from the completion, which deletes the tool-call markup: start llama-server with --special

Credit to LightOn: that error message told us the exact fix. -c 65536 with --parallel 4 gives each slot 16k of context, matching ColGrep's own default agent-context. --host 127.0.0.1 keeps the server local to the machine.

Results with the persistent server:

Built-in runtime Persistent server
One question 5 s (10 s first call) 2.5–4.4 s
10 questions at once one model copy per call 16.5 s total, one copy
GPU memory ~3 GB per concurrent call ~5.5 GB total, held permanently

We didn't re-run the full Opus A/B on the persistent server; these timings are ColGrep's own calls. The answers matched the built-in runtime's, apart from the model's normal sampling variance (it runs at temperature 0.6).

To undo it: colgrep settings --agent-reset.

Field Guide + ColGrep: why they work together

Field Guide is an open-source Claude Code plugin that builds a plain-English guide to every system in your repo. It's one JSON file that assigns every file to a named system and describes each system: what it does, how it works step by step, its key files, its jobs and switches, the traps to avoid, and the rules and tests that guard it. Claude reads it through a one-entry lookup (node docs/field-guide/guide.mjs <system | file path | word>, about 1.5k tokens). A CI check fails whenever a file has no owner or the guide names something that no longer exists, so the guide can't quietly go stale. It's the open-source, generalized version of the internal "System Atlas" we built for SiliconExchange. In a blind A/B on this codebase, it cut ~30% of tokens on code-change tasks at equal accuracy. On quick find-and-explain questions it made no measurable difference.

That last result is the point. The two tools answer different questions:

Field Guide ColGrep-Agent
Answers Which system is this? What must I not break? Which exact lines?
Strongest on Changing code Finding code across several files
Knows Intent, ownership, rules, checks, traps (written down and checked against the code) What the code says (semantic search + a small model reading files)
Can't know The exact line you need right now Why the code is the way it is
Cost ~1.5k tokens per lookup, no GPU ~3–4 s on a local GPU, no API tokens

They fit together in the order a careful developer works. The guide sends the agent to the right system and tells it what the system guards. ColGrep turns "where in here?" into path:start-end without the agent reading half the folder. In our test they stacked: the field guide was present in both arms, and ColGrep still saved 14% on top.

The rule that makes it safe: locate, then Read

The combination has one sharp edge. Claude Code's path-scoped rules (.claude/rules/*.md with paths: globs) load only when the agent reads a matching file with the Read tool. Those rules carry our hardest-won invariants, such as which payment paths must take a lock first. If an agent acts on a snippet ColGrep returned and never opens the file, the rule never loads.

So the line we added to our CLAUDE.md, right under the field-guide pointer, says it outright:

ColGrep (this PC only): colgrep --agent "<question>" LOCATES code (path:start-end, ~4 s, a local model); then Read every file you act on: rules load only through Read, and alone it missed a key file 5 times in 10. Connection error → run %USERPROFILE%\colgrep\start-colgrep-server.cmd.

What we'd tell LightOn

  • The tool works, and the small model is impressively fast on a consumer GPU. Most of a question's time was cold start, not inference.
  • Consider keeping the model loaded by default, or at least have parallel calls share one server instead of loading a copy each. Agents run subagents in parallel, and every copy costs ~3 GB of VRAM.
  • Document --special next to --agent-endpoint. The error message is excellent; it would be better if nobody had to see it.
  • State in the docs which files the indexer skips (it honored .gitignore for us). People will index repos with secrets in them.

Reproduce it

  1. Install ColGrep and the agent model, then run colgrep init in your repo. Check the index against git ls-files.
  2. Start a persistent llama-server with --jinja --special --parallel N, and set colgrep settings --agent-endpoint.
  3. If you use Claude Code, install Field Guide (/plugin marketplace add AlphaGator1/field-guide, then /plugin install field-guide@field-guide) and run /field-guide:build.
  4. Add one instruction for your agent: locate with ColGrep, then Read the files you act on.
  5. Measure on your own questions. Our 14% is one repo's number; yours depends on how much your agent already knows before it starts searching.

Sign up or log in to comment