# Technical Documentation: MinishLab/semble

> ℹ️ **Provenance:** Multi-Tier Ingestion · [CodeWiki Reference](https://codewiki.google/github.com/MinishLab/semble)

### Overview (`README.md`)

<h2 align="center">
  <img width="30%" alt="semble logo" src="https://raw.githubusercontent.com/MinishLab/semble/main/assets/images/semble_logo.png"><br/>
  Fast and Accurate Code Search for Agents<br/>
  <sub>Uses ~99% fewer tokens than grep+read</sub>
</h2>

<div align="center">
  <h2>
    <a href="https://pypi.org/project/semble/"><img src="https://img.shields.io/pypi/v/semble?color=%23007ec6&label=pypi%20package" alt="Package version"></a>
    <a href="https://app.codecov.io/gh/MinishLab/semble">
      <img src="https://codecov.io/gh/MinishLab/semble/graph/badge.svg?token=SZKRFKPPCG" alt="Codecov">
    </a>
    <a href="https://github.com/MinishLab/semble/blob/main/LICENSE">
      <img src="https://img.shields.io/badge/license-MIT-green" alt="License - MIT">
    </a>
  </h2>

[Quickstart](#quickstart) •
[CLI](#cli) •
[MCP Server](#mcp-server) •
[Installation](docs/installation.md) •
[Benchmarks](#benchmarks)


</div>

Semble is a code search library built for agents. It returns the exact code snippets they need instantly, using ~99% fewer tokens than grep+read. Indexing and searching a full codebase end-to-end takes under a second for most repos, matching the retrieval quality of a code-specialized transformer while indexing ~480x faster and querying ~23x faster (see [benchmarks](#benchmarks)). Everything runs on CPU with no API keys, GPU, or external services. Use it as an MCP server, a CLI tool via AGENTS.md, or a dedicated sub-agent, and any coding agent (Claude Code, Cursor, Codex, OpenCode, etc.) gets instant access to any repo.

<picture>
  <source media="(prefers-color-scheme: dark)" srcset="https://raw.githubusercontent.com/MinishLab/semble/main/assets/images/demo-dark.gif">
  <img src="https://raw.githubusercontent.com/MinishLab/semble/main/assets/images/demo-light.gif" width="1000" alt="semble install detecting Claude Code, Cursor, and Codex, then semble search returning ranked code snippets from pydantic" />
</picture>

## Quickstart

Your agent queries Semble in natural language (e.g. `"How is authentication handled?"`) and gets back only the relevant code snippets, without grepping or reading full files.

The fastest way to get started is the interactive installer. Install [uv](https://docs.astral.sh/uv/getting-started/installation/), then run:

```bash
uv tool install semble
semble install
```

`semble install` detects installed coding agents such as Claude Code, Codex, and OpenCode, and then lets you choose which integrations to enable:

- **MCP server**: lets the agent call Semble directly as a tool.
- **Instructions**: adds CLI usage guidance to AGENTS.md / CLAUDE.md.
- **Sub-agent**: installs a dedicated `semble-search` sub-agent.

To undo the setup, run `semble uninstall`.

For manual setup instructions (MCP config per agent, AGENTS.md snippet, sub-agent files), see the [installation docs](docs/installation.md).

<details>
<summary>Updating Semble</summary>

```bash
uv tool upgrade semble   # upgrade
uv cache clean semble    # for MCP users (restart your MCP client after)
```

</details>

<details>
<summary>Unattended install</summary>

For sandboxed or scripted environments, skip the prompts with `--agent` and, optionally, `--type`:

```bash
semble install --agent claude --type mcp subagent --yes
```

`--agent` accepts one or more agent ids (e.g. `claude`, `codex`, `pi`); `--type` accepts `mcp`, `instructions`, `subagent`, or `all` (default: all); `--yes` skips the confirmation prompt (requires `--agent` for a fully non-interactive run).

</details>

## Main Features

- **Fast**: indexes an average repo in ~500 ms and answers queries in ~1 ms, all on CPU.
- **Accurate**: NDCG@10 of 0.855 on our [benchmarks](#benchmarks), on par with code-specialized transformer models, at a fraction of the size and cost.
- **Token-efficient**: returns only the relevant chunks, using [~99% fewer tokens than grep+read](#benchmarks).
- **Zero setup**: runs on CPU with no API keys, GPU, or external services required.
- **MCP server**: works with Claude Code, Cursor, Codex, OpenCode, VS Code, and any other MCP-compatible agent.
- **Local and remote**: pass a local path or a git URL, or several of them to search related repos together.

## CLI

Semble also ships as a standalone CLI. This is useful in scripts or anywhere you want search results without an MCP session. Indexes are built and cached on first run, and invalidated automatically when files change.

```bash
# Search a local repo (index is built and cached automatically)
semble search "authentication flow" ./my-project

# Search a remote repo (cloned on demand)
semble search "save model to disk" https://github.com/MinishLab/model2vec

# Search several repos at once (results are prefixed with the repo name)
semble search "invoice endpoint" ./service-a ./service-b

# Limit results
semble search "save model to disk" ./my-project --top-k 10

# Search docs/config/everything instead of just code
semble search "deployment guide" ./my-project --content docs   # or: config, all

# Find code similar to a known location
semble find-related src/auth.py 42 ./my-project

# Show only the first N lines of each result's snippet (0 = path/line range only)
semble search "authentication flow" ./my-project --max-snippet-lines 10
```

`--content` accepts `code` (default), `docs`, `config`, or `all`. `--format` accepts `json` (default) or `text`. `path` defaults to the current directory when omitted; git URLs are accepted. If `semble` is not on `$PATH`, use `uvx --from "semble[mcp]" semble` in its place. `semble --version` (or `-V`) prints the installed version.

Passing several paths or URLs searches them as one corpus, so a query from repo A can find an endpoint defined in repo B. Each index is cached per repo and merged at query time. Result paths are prefixed with the repo name (`service-b/api/invoices.py`) and the output includes a `repos` map from prefix to absolute path or URL. Pass the prefixed path to `find-related` to search across all repos from a known location.

<details>
<summary>Controlling which files are indexed</summary>

Semble reads `.gitignore` and `.sembleignore` files to determine which files to index. Both files use standard gitignore syntax and their patterns are merged. `.sembleignore` lets you add semble-specific rules without touching `.gitignore`. Rules are applied recursively, so a `.sembleignore` in a subdirectory applies to that subtree.

**Excluding files:** add patterns the same way you would in `.gitignore`:

```
# .sembleignore
generated/     # exclude generated dir
*.pb.go.       # exclude Go protobuf files
```

**Including non-default extensions:** prefix the extension pattern with `!` to force-include files that semble wouldn't index by default:

```
# .sembleignore
!*.proto       # include Protobuf files
!*.cob         # include COBOL files
```

Run `semble file-types` to see which extensions are indexed per content type. Extensions not listed (including data files such as `.json` and `.csv`) are not indexed by default and can be force-included with a `!` pattern as above.

Semble also always skips a set of well-known non-source directories regardless of ignore files (e.g. `node_modules/`, `.venv/`, `dist/`, `build/`, `__pycache__/`, and similar).

</details>

<details>
<summary>Savings</summary>

`semble savings` shows how many tokens semble has saved across all your searches:

```bash
semble savings
```

```
  Semble Token Savings
  ════════════════════════════════════════════════════════════════════════

  Total saved:  ~714.2M tokens  (94%)
  Total calls:  14.3k
  Efficiency:  ███████████████████████░  94%

  By Period
  ────────────────────────────────────────────────────────────────────────
  Period             Calls           Saved  Ratio
  ────────────────────────────────────────────────────────────────────────
  Today                198    ~1.4M tokens  ███████████████████████░  95%
  Last 7 days        13.1k  ~707.2M tokens  ███████████████████████░  94%
  All time           14.3k  ~714.2M tokens  ███████████████████████░  94%

  By Call Type
  ────────────────────────────────────────────────────────────────────────
  #     Call type            Calls  Share
  ────────────────────────────────────────────────────────────────────────
  1.    search               14.1k  ████████████████    99%
  2.    find_related           205  █░░░░░░░░░░░░░░░     1%
  ════════════════════════════════════════════════════════════════════════
```


Savings are calculated as follows: for each call, semble records the total character count of the unique files containing returned chunks and the character count of the snippets returned. Estimated tokens saved is `(file chars − snippet chars) / 4` (4 chars per token). This is a conservative estimate: the baseline is reading matched files in full, which is how coding agents often explore unfamiliar code.

</details>

<details>
<summary>Storage</summary>

By default, your Semble savings statistics and any saved indexes are stored in the OS cache folder (`~/Library/Caches/semble/` on macOS, `~/.cache/semble/` on Linux, `%LOCALAPPDATA%\semble\Cache\` on Windows). To override this location you can supply an environment variable `SEMBLE_CACHE_LOCATION` which should be the full path to the target cache location e.g. `~/my-folder/my-caches/semble`.

Files larger than 1 MB are skipped during indexing to keep index builds lean. Skipped files are reported as a warning at index time. If you work with large generated or ingested documents, you can raise (or lower) this limit with the `SEMBLE_MAX_FILE_BYTES` environment variable (in bytes).

On first use, Semble also downloads the embedding model from Hugging Face and caches it in the standard Hugging Face cache (`~/.cache/huggingface/` by default, or `$HF_HOME` if set); this only happens once and requires network access.

Use `semble clear` to remove cached data: `semble clear index` (saved indexes), `semble clear savings` (usage stats), `semble clear orphans` (indexes for repos no longer present on disk), or `semble clear all` (everything).

</details>

<details>
<summary>Library usage</summary>

Semble can also be used as a Python library for programmatic access, useful when building custom tooling or integrating search directly into your own code.

```python
from semble import ContentType, SembleIndex

# Index a local directory (code only, the default)
index = SembleIndex.from_path("./my-project")

# Index docs and prose (markdown, rst, etc.)
index = SembleIndex.from_path("./my-project", content=ContentType.DOCS)

# Index everything (code, docs, and config)
index = SembleIndex.from_path("./my-project", content=[ContentType.CODE, ContentType.DOCS, ContentType.CONFIG])

# Index code and docs together
index = SembleIndex.from_path("./my-project", content=[ContentType.CODE, ContentType.DOCS])

# Index a remote git repository
index = SembleIndex.from_git("https://github.com/MinishLab/model2vec")

# Merge indexes from several repos into one (chunk paths are prefixed with the repo name)
index = SembleIndex.merge([("./service-a", SembleIndex.from_path("./service-a")), ("./service-b", SembleIndex.from_path("./service-b"))])

# Search the index with a natural-language or code query
results = index.search("save model to disk", top_k=3)

# Find code similar to a specific result
related = index.find_related(results[0], top_k=3)

# Each result exposes the matched chunk
result = results[0]
result.chunk.file_path   # "model2vec/model.py"
result.chunk.start_line  # 127
result.chunk.end_line    # 150
result.chunk.content     # "def save_pretrained(self, path: PathLike, ..."
```

When many files need indexing, the CLI and MCP server chunk them in 4 worker processes (`SEMBLE_INDEX_WORKERS` sets the number, `0` turns them off). In the Python API they are off by default. To turn them on, set `SEMBLE_INDEX_WORKERS` and call Semble from inside an `if __name__ == "__main__":` block, since each worker re-imports your script.

</details>

## MCP Server

Semble runs as an MCP server so agents can search any codebase directly as a native tool call. Repos are indexed on demand and cached; local paths are re-indexed automatically on file changes.

| Tool | Description |
|------|-------------|
| `search` | Search a codebase with a natural-language or code query. Pass `repo` as a local path or an https:// git URL (or a list of them to search several repos together) and `content` as `code`, `docs`, `config`, or `all` (default: `code`). |
| `find_related` | Given a file path and line number, return chunks semantically similar to the code at that location. |

For per-agent setup instructions, see the [installation docs](docs/installation.md#mcp-server).


## Benchmarks

We benchmark quality and speed across ~1,250 queries over 63 repositories in 19 languages (left), and token efficiency against grep+read at equivalent recall levels (right).

<table>
<tr>
<td><img src="https://raw.githubusercontent.com/MinishLab/semble/main/assets/images/speed_vs_ndcg_cold.png" alt="Speed vs quality"></td>
<td><img src="https://raw.githubusercontent.com/MinishLab/semble/main/assets/images/token_efficiency.png" alt="Token efficiency: recall vs. retrieved tokens"></td>
</tr>
</table>

The quality benchmark (left) scores retrieval quality (NDCG@10) against total latency; semble matches the quality of the 137M-parameter [CodeRankEmbed](https://huggingface.co/nomic-ai/CodeRankEmbed) while indexing 480x faster. The token efficiency benchmark (right) measures how many tokens each method needs to reach a given recall level; semble uses 99% fewer tokens on average and hits 96% recall at only 2k tokens, while grep+read needs a full 100k context window to reach 85%. See [benchmarks](benchmarks/README.md) for per-language results, ablations, and full methodology.

## How it works

Semble splits each file into code-aware chunks using [tree-sitter](https://github.com/tree-sitter/py-tree-sitter), then scores every query against the chunks with two complementary retrievers: static [Model2Vec](https://github.com/MinishLab/model2vec) embeddings using the code-specialized [potion-code-16M-v2](https://huggingface.co/minishlab/potion-code-16M-v2) model for semantic similarity, and BM25 for lexical matches on identifiers and API names. The two score lists are fused with Reciprocal Rank Fusion (RRF).

After fusing, results are reranked with a set of code-aware signals:

<details>
<summary><b>Ranking signals</b></summary>

- **Adaptive weighting.** Symbol-like queries (`Foo::bar`, `_private`, `getUserById`) get more lexical weight, while natural-language queries stay balanced between semantic and lexical retrievers.
- **Definition boosts.** A chunk that defines the queried symbol (a `class`, `def`, `func`, etc.) is ranked above chunks that merely reference it.
- **Identifier stems.** Query tokens are stemmed and matched against identifier stems in a chunk, giving an additional weight to chunks that contain them. For example, querying `parse config` boosts chunks containing `parseConfig`, `ConfigParser`, or `config_parser`.
- **File coherence.** When multiple chunks from the same file match the query, the file is boosted so the top result reflects broad file-level relevance rather than a single out-of-context chunk.
- **Noise penalties.** Test files, `compat/`/`legacy/` shims, example code, and `.d.ts` declaration stubs are down-ranked so canonical implementations surface first.

</details>

Because the embedding model is static with no transformer forward pass at query time, all of this runs in milliseconds on CPU.

Indexes are cached to disk automatically on the first search. On subsequent runs, Semble walks the file tree and compares modification times; added, removed, or changed files are reindexed incrementally, without rebuilding the rest of the index. A full rebuild only happens if the indexing settings change (e.g., after a semble upgrade that changes the model, chunking, or cache format). In MCP mode, the index is checked and refreshed automatically as files change, so results stay current across the session.

### Using a custom model

If you would like to use another model, you can set your `SEMBLE_MODEL_NAME` environment variable to a local path or Hugging Face repository. This path is read verbatim, and should contain a [`Model2Vec`](https://github.com/MinishLab/model2vec) compatible model. This is particularly useful if you can't access Hugging Face at runtime.

## Acknowledgements

Thanks to [Greptile](https://greptile.com) for providing free access to their AI code review platform.

## License

MIT

## Citing

If you use Semble in your research, please cite the following:

```bibtex
@software{minishlab2026semble,
  author       = {{van Dongen}, Thomas and Stephan Tulkens},
  title        = {Semble: Fast and Accurate Code Search for Agents},
  year         = {2026},
  publisher    = {Zenodo},
  doi          = {10.5281/zenodo.19785932},
  url          = {https://github.com/MinishLab/semble},
  license      = {MIT}
}
```

---

### Overview (`benchmarks/README.md`)

# Benchmarks

Quality and speed benchmarks for `semble`.

- [Main results](#main-results)
- [Token efficiency](#token-efficiency)
- [By language](#by-language)
- [Ablations](#ablations)
- [Dataset](#dataset)
- [Methods](#methods)
- [Excluded methods](#excluded-methods)
- [Running the benchmarks](#running-the-benchmarks)

## Main results

Quality and speed across all methods.

| Method               |   NDCG@10 |      Index |   Query p50 |
| -------------------- | --------: | ---------: | ----------: |
| **semble**           | **0.855** | **240 ms** | **0.69 ms** |
| CodeRankEmbed        |     0.839 |      116 s |       16 ms |
| ColGREP              |     0.693 |      5.4 s |      122 ms |
| BM25                 |     0.673 |      47 ms |     0.17 ms |
| zvec-grep            |     0.670 |      3.4 s |      391 ms |
| ck                   |     0.642 |       96 s |      187 ms |
| codebase-memory-mcp  |     0.630 |     454 ms |       46 ms |
| grepai               |     0.561 |       35 s |       48 ms |
| probe                |     0.387 |          — |      207 ms |
| cs                   |     0.200 |          — |       22 ms |
| ripgrep              |     0.126 |          — |       14 ms |

|  |  |
| :-----------------------------------------------------------------: | :-----------------------------------------------------------------: |
|          _Time to first result (index + query) vs NDCG@10_          |             _Query latency on a warm index vs NDCG@10_              |

semble matches the NDCG@10 of the 137M-param CodeRankEmbed while winning index time by ~480x and query latency by ~23x.

NDCG@10 is averaged across all queries. Speed numbers use one repo per language, CPU only: cold-start index time and warm query p50 (median across 5 consecutive runs).

## Token efficiency

Coding agents (Claude Code, OpenCode, etc.) typically find code by running `grep` on keywords and reading the matched files. We model that workflow and compare it against semble's chunk retrieval across our full benchmark of 1251 queries.



### Expected tokens per query

For each query: tokens consumed at first relevant hit, or 32k if the method never finds anything. Averaged across all 1251 queries.

| Method              | Expected tokens |       Savings |
| ------------------- | --------------: | ------------: |
| ripgrep + read file |          45,587 |      baseline |
| **semble**          |         **353** | **99% fewer** |

### Recall at fixed token budgets

A relevant file is "covered" once any retrieved unit comes from it.

| Method              |       500 |        1k |        2k |        4k |        8k |       16k |       32k |
| ------------------- | --------: | --------: | --------: | --------: | --------: | --------: | --------: |
| **semble**          | **0.843** | **0.921** | **0.963** | **0.989** | **0.995** | **0.996** | **0.996** |
| ripgrep + read file |     0.001 |     0.008 |     0.037 |     0.086 |     0.207 |     0.374 |     0.583 |

<details>
<summary>Methodology</summary>

semble returns the top-50 ranked chunks. `ripgrep+read` splits the query into keywords (dropping stopwords and short words), runs `rg --fixed-strings --ignore-case` for each keyword, then reads matched files in full ranked by how many distinct keywords they contain. Both methods search the same set of file types and ignored directories. Tokens are counted with `cl100k_base` via `tiktoken`. A relevant file is "covered" once any retrieved unit overlaps its annotated span.

</details>

## By language

NDCG@10 per language, sorted by semble. CRE = CodeRankEmbed, cbm = codebase-memory-mcp. Best score per row is bolded.

| Language    |    semble |       CRE |   ColGREP |  zvec-grep |        ck |       cbm |    grepai |     probe |        cs |   ripgrep |
| ----------- | --------: | --------: | --------: | ---------: | --------: | --------: | --------: | --------: | --------: | --------: |
| scala       |     0.921 | **0.925** |     0.765 |      0.799 |     0.717 |     0.704 |     0.330 |     0.392 |     0.111 |     0.180 |
| javascript  |     0.911 | **0.925** |     0.823 |      0.760 |     0.772 |     0.770 |     0.675 |     0.588 |     0.171 |     0.176 |
| go          | **0.910** |     0.713 |     0.785 |      0.512 |     0.458 |     0.506 |     0.722 |     0.410 |     0.183 |     0.133 |
| ruby        | **0.909** |     0.905 |     0.708 |      0.758 |     0.738 |     0.689 |     0.643 |     0.382 |     0.255 |     0.230 |
| elixir      | **0.905** |     0.893 |     0.808 |      0.811 |     0.786 |     0.506 |     0.669 |     0.412 |     0.397 |     0.134 |
| zig         |     0.899 | **0.911** |     0.474 |      0.664 |     0.511 |     0.766 |     0.755 |     0.369 |     0.121 |     0.000 |
| cpp         |     0.881 | **0.897** |     0.626 |      0.677 |     0.687 |     0.630 |     0.731 |     0.375 |     0.262 |     0.126 |
| python      |     0.876 | **0.878** |     0.777 |      0.704 |     0.721 |     0.643 |     0.634 |     0.488 |     0.305 |     0.202 |
| csharp      | **0.872** |     0.848 |     0.614 |      0.609 |     0.548 |     0.775 |     0.277 |     0.392 |     0.248 |     0.117 |
| php         | **0.864** |     0.847 |     0.663 |      0.583 |     0.615 |     0.608 |     0.402 |     0.340 |     0.180 |     0.123 |
| swift       | **0.848** |     0.845 |     0.710 |      0.709 |     0.672 |     0.630 |     0.429 |     0.280 |     0.151 |     0.160 |
| bash        | **0.840** |     0.834 |     0.706 |      0.725 |     0.677 |     0.768 |     0.723 |     0.226 |     0.170 |     0.000 |
| lua         | **0.835** |     0.829 |     0.798 |      0.736 |     0.738 |     0.591 |     0.699 |     0.336 |     0.050 |     0.000 |
| java        | **0.828** |     0.790 |     0.641 |      0.685 |     0.606 |     0.554 |     0.386 |     0.536 |     0.136 |     0.198 |
| rust        | **0.826** |     0.754 |     0.662 |      0.541 |     0.419 |     0.454 |     0.519 |     0.242 |     0.193 |     0.162 |
| kotlin      |     0.803 | **0.823** |     0.637 |      0.628 |     0.587 |     0.611 |     0.478 |     0.335 |     0.170 |     0.166 |
| haskell     |     0.775 | **0.811** |     0.683 |      0.689 |     0.733 |     0.624 |     0.483 |     0.313 |     0.160 |     0.000 |
| c           |     0.750 | **0.771** |     0.676 |      0.598 |     0.606 |     0.655 |     0.555 |     0.384 |     0.175 |     0.000 |
| typescript  | **0.742** |     0.671 |     0.430 |      0.476 |     0.456 |     0.455 |     0.394 |     0.354 |     0.145 |     0.128 |
| **overall** | **0.855** |     0.839 |     0.693 |      0.670 |     0.642 |     0.630 |     0.561 |     0.387 |     0.200 |     0.126 |

## Ablations

`raw` returns retrieval scores directly; `+ ranking` feeds them through semble's hybrid ranker.

| Retrieval              |   Raw | + ranking |
| ---------------------- | ----: | --------: |
| BM25                      | 0.686 |     0.838 |
| potion-code-16M-v2        | 0.652 |     0.824 |
| BM25 + potion-code-16M-v2 |     — | **0.855** |

<details>
<summary>By query category</summary>

| Mode                               | Architecture |  Semantic |    Symbol |
| ---------------------------------- | -----------: | --------: | --------: |
| BM25 raw                           |        0.642 |     0.680 |     0.748 |
| potion-code-16M raw                |        0.636 |     0.671 |     0.620 |
| semble BM25 (+ ranking)            |        0.794 |     0.817 |     0.962 |
| semble potion-code-16M (+ ranking) |        0.768 |     0.819 |     0.917 |
| **semble hybrid**                  |    **0.808** | **0.847** | **0.954** |

</details>

## Dataset

~1,250 queries over 63 repositories in 19 languages, grouped into three categories:

| Category     | Queries | What it tests                                            |
| ------------ | ------: | -------------------------------------------------------- |
| semantic     |     711 | Code that implements a specific behavior or concept      |
| architecture |     343 | Design decisions, module boundaries, structural patterns |
| symbol       |     204 | Named entity lookup (function, class, type, variable)    |

<details>
<summary>Notes</summary>

**Languages**: three repos per language (nine for Python): bash, C, C++, C#, Elixir, Go, Haskell, Java, JavaScript, Kotlin, Lua, PHP, Python, Ruby, Rust, Scala, Swift, TypeScript, Zig. Repos are pinned by revision in `repos.json`.

**How the benchmark was built**: queries and ground-truth relevance labels are generated by Claude Sonnet 4.6. The same model is used as LLM-as-judge to verify label quality.

</details>

## Methods

- **[ripgrep](https://github.com/BurntSushi/ripgrep)**: fast regex search over files, included as a raw keyword-match baseline.
- **[probe](https://github.com/buger/probe)**: BM25 keyword ranking backed by tree-sitter parse trees. We use its free `probe search` mode; its `probe agent` mode (which does query rewriting) needs a paid LLM API key and isn't tested here.
- **[cs (Code Spelunker)](https://github.com/boyter/cs)**: structural BM25 ranker which ranks matches differently in code/comments/strings).
- **[ColGREP](https://github.com/lightonai/next-plaid/tree/main/colgrep)**: late-interaction code retrieval built on next-plaid with the [LateOn-Code-edge](https://huggingface.co/lightonai/LateOn-Code-edge) model.
- **[grepai](https://github.com/nicholasgasior/grepai)**: semantic search using [nomic-embed-text](https://huggingface.co/nomic-ai/nomic-embed-text-v1) (137M params) via a local Ollama daemon.
- **[codebase-memory-mcp](https://github.com/DeusData/codebase-memory-mcp)**: code intelligence engine that indexes a repo into a SQLite/graph store. We benchmark its `search_graph` tool in `fast` mode, which does BM25 full-text search with structural boosting.
- **[ck](https://github.com/BeaconBay/ck)**: hybrid regex + semantic search using [BAAI/bge-small-en-v1.5](https://huggingface.co/BAAI/bge-small-en-v1.5).
- **[zvec-grep](https://github.com/zvec-ai/zvec-grep)**: [potion-code-16M-v2](https://huggingface.co/minishlab/potion-code-16M-v2) static embeddings + BM25. Benchmarked in `direct` mode.
- **[CodeRankEmbed](https://huggingface.co/nomic-ai/CodeRankEmbed)**: 137M-param transformer embedding model for code retrieval, used for semantic-only dense search.
- **[semble](https://github.com/your-repo/semble)**: this library. [potion-code-16M-v2](https://huggingface.co/minishlab/potion-code-16M-v2) static embeddings + BM25 + the semble reranking stack.

## Excluded methods

The following tools were considered but not included in the main comparison:

- **[codanna](https://codanna.io)**: symbol-level semantic search with fastembed. Excluded because it does not support Haskell, Bash, Zig, Scala, Elixir, or Ruby (6 of the 19 benchmark languages).
- **[claude-context](https://github.com/zilliztech/claude-context)**: retrieval-augmented code search using OpenAI embeddings and a vector database. Excluded because it requires a paid OpenAI API key and a running vector-DB service.
- **[GitNexus](https://github.com/abhigyanpatwari/GitNexus)**: knowledge-graph code search (BM25 + local embeddings + RRF). Excluded because it does not support Bash, Elixir, Haskell, Lua, Scala, or Zig (6 of the 19 benchmark languages).
- **[codegraph](https://github.com/colbymchenry/codegraph)**: SQLite FTS5 symbol search + graph traversal. Excluded because it does not support Haskell, Bash, Elixir, or Zig (4 of the 19 benchmark languages).
- **[tgrep](https://github.com/microsoft/tgrep)**: indexed regex search. Excluded
  because its results are nearly identical to ripgrep (0.1255 vs 0.1259 NDCG@10), and
  ripgrep is already fast enough that indexing adds nothing in our benchmarks.

## Running the benchmarks

Repos are pinned in `repos.json` and cloned into `~/.cache/semble-bench`:

```bash
uv run python -m benchmarks.sync_repos          # clone / update
uv run python -m benchmarks.sync_repos --check  # verify only
```

All tools run CPU-only. semble uses `minishlab/potion-code-16M-v2`; CodeRankEmbed uses `nomic-ai/CodeRankEmbed` (137M params). The speed benchmark touches one repo per language with a cold-start index and 5 query runs per repo.

<details>
<summary>semble</summary>

```bash
uv run python -m benchmarks.run_benchmark
uv run python -m benchmarks.run_benchmark --repo fastapi --repo axios
uv run python -m benchmarks.run_benchmark --language python
```

Full runs write to `benchmarks/results/semble-hybrid-<sha12>.json`.

</details>

<details>
<summary>Speed benchmark</summary>

```bash
uv run python -m benchmarks.speed_benchmark
uv run python -m benchmarks.speed_benchmark --tools semble zvec-grep   # subset of tools
uv run python -m benchmarks.speed_benchmark --repo flask --repo axios  # subset of repos
```

Writes to `benchmarks/results/speed-<sha12>.json`.

</details>

<details>
<summary>Ablations</summary>

```bash
uv run python -m benchmarks.baselines.ablations
uv run python -m benchmarks.baselines.ablations --mode bm25
uv run python -m benchmarks.baselines.ablations --mode semble-semantic
```

</details>

<details>
<summary>probe</summary>

Needs `probe` on `$PATH` (`npm install @buger/probe`).

```bash
uv run python -m benchmarks.baselines.probe
uv run python -m benchmarks.baselines.probe --repo fastapi --repo axios
```

</details>

<details>
<summary>grepai</summary>

Needs `grepai` on `$PATH` and Ollama running with `nomic-embed-text` pulled:

```bash
ollama pull nomic-embed-text
```

```bash
uv run python -m benchmarks.baselines.grepai
uv run python -m benchmarks.baselines.grepai --repo fastapi --repo axios
```

Large repos take several minutes to index. Use `--timeout <seconds>` (default 120) for repos with many files:

```bash
uv run python -m benchmarks.baselines.grepai --timeout 1800 --output results.json
```

The `--output` flag enables resume mode: already-completed repos are skipped on restart.

</details>

<details>
<summary>cs (Code Spelunker)</summary>

Needs `cs` on `$PATH` (`go install github.com/boyter/cs/v3@latest`).

```bash
uv run python -m benchmarks.baselines.cs
uv run python -m benchmarks.baselines.cs --repo fastapi --repo axios
```

</details>

<details>
<summary>codebase-memory-mcp</summary>

Needs `codebase-memory-mcp` on `$PATH` (`uv tool install codebase-memory-mcp`, or `pip`/`npm`/`brew`). First run downloads a small platform binary.

```bash
uv run python -m benchmarks.baselines.codebase_memory
uv run python -m benchmarks.baselines.codebase_memory --repo fastapi --repo axios
```

Each repo is indexed under a `semble-bench-<repo>` project name and deleted again after evaluation, so it doesn't collide with any projects you have indexed for real use.

</details>

<details>
<summary>ck</summary>

Needs `ck` on `$PATH` (`cargo install ck-search` or `npm install @beaconbay/ck-search`). Requires outbound HTTPS to Hugging Face to download its `BAAI/bge-small-en-v1.5` embedding model on first run.

```bash
uv run python -m benchmarks.baselines.ck
uv run python -m benchmarks.baselines.ck --repo fastapi --repo axios
```

</details>

<details>
<summary>zvec-grep</summary>

Needs `zg` on `$PATH` (`npm install @zvec/zvec-grep@0.2.1`, requires Node.js 22+). Downloads the `potion-code-16m-v2` embedding model on first index. The published numbers were measured with 0.2.1; the runner records the installed version in its results file, and pinning matters because it parses `zg`'s human-readable output.

```bash
uv run python -m benchmarks.baselines.zvecgrep
uv run python -m benchmarks.baselines.zvecgrep --repo fastapi --repo axios
```

</details>

<details>
<summary>ripgrep</summary>

Needs `rg` on `$PATH` (`brew install ripgrep` / `apt install ripgrep`).

```bash
uv run python -m benchmarks.baselines.ripgrep
uv run python -m benchmarks.baselines.ripgrep --no-fixed-strings
```

</details>

<details>
<summary>ColGREP</summary>

Needs the `colgrep` binary on `$PATH`.

```bash
uv run python -m benchmarks.baselines.colgrep
uv run python -m benchmarks.baselines.colgrep --repo fastapi --repo axios
```

Runs with `--code-only` everywhere except bash repos (bash-it, bats-core, nvm), which use `--no-code-only` because ColGREP's code filter excludes `.sh`/`.bash` files.

</details>

<details>
<summary>CodeRankEmbed</summary>

Requires the `benchmark` extra (`uv sync --extra benchmark`).

```bash
uv run python -m benchmarks.baselines.coderankembed
uv run python -m benchmarks.baselines.coderankembed --repo fastapi --repo axios
```

</details>

<details>
<summary>Context-efficiency benchmark</summary>

Requires the `benchmark` extra (`uv sync --extra benchmark`) and `rg` on `$PATH`.

```bash
# Recall vs. token-budget across all queries; plots automatically.
uv run python -m benchmarks.token_efficiency recall
uv run python -m benchmarks.token_efficiency recall --repo fastapi

# Regenerate the plot from a saved recall payload.
uv run python -m benchmarks.token_efficiency plot
```

Writes `benchmarks/results/token-efficiency-<sha12>.json` and `assets/images/token_efficiency.png`.

</details>

<details>
<summary>Plots</summary>

```bash
uv run python -m benchmarks.plot
```

Writes `speed_vs_ndcg_cold.png` and `speed_vs_ndcg_warm.png` to `assets/images/`.

</details>

---

### Installation (`docs/installation.md`)

# Installation

## Recommended: `semble install`

The interactive installer detects your installed agents and configures any combination of three integrations globally:

- **[MCP server](#mcp-server)**: exposes Semble as a native tool your agent can call directly.
- **[AGENTS.md](#instructions-agentsmd--claudemd)**: adds a Semble usage guide to the agent's config file (`CLAUDE.md`, `AGENTS.md`, etc.).
- **[Sub-agent](#sub-agent)**: installs a dedicated `semble-search` sub-agent for harnesses that support it.

Install the CLI with [uv](https://docs.astral.sh/uv/getting-started/installation/), then run:

```bash
uv tool install semble
semble install
```

To undo:

```bash
semble uninstall
```

Supported agents: Claude Code, Cursor, Gemini CLI, Kiro, OpenCode, GitHub Copilot, Codex, VS Code, Windsurf, Zed, Reasonix, Pi, Command Code, Antigravity, ZCode, Grok Build, Qwen Code, Cline, and Kilo Code.

> **Pi prerequisite:** Pi requires the MCP extension to be installed before semble can connect. Run `pi install npm:pi-mcp-extension` once, then `semble install`.

### Unattended install

For sandboxed or scripted environments, pass `--agent` to skip the interactive prompts:

```bash
semble install --agent claude pi --type mcp subagent --yes
```

- `--agent` — one or more agent ids (see the list above; use the lowercase form, e.g. `claude`, `codex`, `pi`).
- `--type` — one or more of `mcp`, `instructions`, `subagent`, or `all` (default: all). Requires `--agent`.
- `-y`/`--yes` — skip the confirmation prompt. Requires `--agent` for a fully non-interactive run.

`semble uninstall` accepts the same flags.

### Keeping installed configuration up to date

The MCP server config, `AGENTS.md`/`CLAUDE.md` instructions, and sub-agent files that `semble install` writes all pin `uvx` to the exact semble version you have installed (`semble[mcp]==X.Y.Z`), so agents keep calling the version the instructions were written for rather than whatever is newest on PyPI. After upgrading (`uv tool upgrade semble` or `pip install --upgrade semble`), rerun `semble install`. This is idempotent and rewrites the pin (and anything else that changed) in place for every agent you select.

---

## Manual setup

### MCP server

> Requires [uv](https://docs.astral.sh/uv/getting-started/installation/) to be installed.

<details>
<summary>Claude Code</summary>

```bash
claude mcp add semble -s user -- uvx --from "semble[mcp]" semble
```

</details>

<details>
<summary>Cursor</summary>

Add to `~/.cursor/mcp.json` (or `.cursor/mcp.json` in your project):

```json
{
  "mcpServers": {
    "semble": {
      "command": "uvx",
      "args": ["--from", "semble[mcp]", "semble"]
    }
  }
}
```

</details>

<details>
<summary>Codex</summary>

Add to `~/.codex/config.toml`:

```toml
[mcp_servers.semble]
command = "uvx"
args = ["--from", "semble[mcp]", "semble"]
```

</details>

<details>
<summary>OpenCode</summary>

Add to `~/.config/opencode/opencode.jsonc`:

```json
{
  "mcp": {
    "semble": {
      "type": "local",
      "command": ["uvx", "--from", "semble[mcp]", "semble"]
    }
  }
}
```

</details>

<details>
<summary>VS Code</summary>

Add to `.vscode/mcp.json` in your project (or your user profile's `mcp.json`):

```json
{
  "servers": {
    "semble": {
      "command": "uvx",
      "args": ["--from", "semble[mcp]", "semble"]
    }
  }
}
```

</details>

<details>
<summary>GitHub Copilot CLI</summary>

Add to `~/.copilot/mcp-config.json`:

```json
{
  "mcpServers": {
    "semble": {
      "command": "uvx",
      "args": ["--from", "semble[mcp]", "semble"]
    }
  }
}
```

</details>

<details>
<summary>Windsurf</summary>

Add to `~/.codeium/windsurf/mcp_config.json`:

```json
{
  "mcpServers": {
    "semble": {
      "command": "uvx",
      "args": ["--from", "semble[mcp]", "semble"]
    }
  }
}
```

</details>

<details>
<summary>Gemini CLI</summary>

Add to `~/.gemini/settings.json`:

```json
{
  "mcpServers": {
    "semble": {
      "command": "uvx",
      "args": ["--from", "semble[mcp]", "semble"]
    }
  }
}
```

</details>

<details>
<summary>Kiro</summary>

Add to `~/.kiro/settings/mcp.json` (or `.kiro/settings/mcp.json` in your project):

```json
{
  "mcpServers": {
    "semble": {
      "command": "uvx",
      "args": ["--from", "semble[mcp]", "semble"]
    }
  }
}
```

</details>

<details>
<summary>Zed</summary>

Add to `~/.config/zed/settings.json` (or `.zed/settings.json` in your project):

```json
{
  "context_servers": {
    "semble": {
      "source": "custom",
      "command": "uvx",
      "args": ["--from", "semble[mcp]", "semble"]
    }
  }
}
```

</details>

<details>
<summary>Reasonix</summary>

Add to `~/.reasonix/config.json` (the backwards-compatible MCP config path read by all Reasonix versions):

```json
{
  "mcpServers": {
    "semble": {
      "command": "uvx",
      "args": ["--from", "semble[mcp]", "semble"]
    }
  }
}
```

</details>

<details>
<summary>Pi</summary>

First install the Pi MCP extension (one-time prerequisite):

```bash
pi install npm:pi-mcp-extension
```

Then add to `~/.pi/agent/mcp.json`:

```json
{
 "mcpServers": {
 "semble": {
 "command": "uvx",
 "args": ["--from", "semble[mcp]", "semble"]
 }
 }
}
```

</details>

<details>
<summary>Antigravity</summary>

Add to `~/.gemini/config/mcp_config.json`:

```json
{
  "mcpServers": {
    "semble": {
      "command": "uvx",
      "args": ["--from", "semble[mcp]", "semble"]
    }
  }
}
```

</details>

<details>
<summary>Command Code</summary>

Add to `~/.commandcode/mcp.json`:

```json
{
 "mcpServers": {
 "semble": {
 "command": "uvx",
 "args": ["--from", "semble[mcp]", "semble"]
 }
 }
}
```

Or use the CLI:

```bash
cmd mcp add --scope user semble -- uvx --from "semble[mcp]" semble
```

</details>

<details>
<summary>ZCode</summary>

Add to `~/.zcode/cli/config.json` under the nested `mcp.servers` key (or use Settings -> MCP Servers -> Full configuration mode):

```json
{
  "mcp": {
    "servers": {
      "semble": {
        "command": "uvx",
        "args": ["--from", "semble[mcp]", "semble"],
        "type": "stdio"
      }
    }
  }
}
```

</details>

<details>
<summary>Grok Build</summary>

Add to `~/.grok/config.toml`:

```toml
[mcp_servers.semble]
command = "uvx"
args = ["--from", "semble[mcp]", "semble"]
```

</details>

<details>
<summary>Qwen Code</summary>

Add to `~/.qwen/settings.json`:

```json
{
  "mcpServers": {
    "semble": {
      "command": "uvx",
      "args": ["--from", "semble[mcp]", "semble"]
    }
  }
}
```

</details>

<details>
<summary>Cline</summary>

Add to `~/.cline/data/settings/cline_mcp_settings.json` (shared by the Cline IDE extensions and CLI):

```json
{
  "mcpServers": {
    "semble": {
      "transport": {
        "type": "stdio",
        "command": "uvx",
        "args": ["--from", "semble[mcp]", "semble"]
      }
    }
  }
}
```

Or use the CLI:

```bash
cline mcp add semble --yes -- uvx --from "semble[mcp]" semble
```

</details>

<details>
<summary>Kilo Code</summary>

Add to `~/.config/kilo/kilo.jsonc`:

```json
{
  "mcp": {
    "semble": {
      "type": "local",
      "command": ["uvx", "--from", "semble[mcp]", "semble"]
    }
  }
}
```

</details>

The MCP server indexes each requested content selection on first use and caches it separately. Searches default to code; append `--content docs`, `--content config`, or `--content all` to the server command to change that default. The `content` argument on an individual MCP search overrides it. For example, in Claude Code:

```bash
claude mcp add semble -s user -- uvx --from "semble[mcp]" semble --content all
```

Indexes stay in memory for the lifetime of the server. To free memory while idle, set `SEMBLE_MCP_IDLE_TIMEOUT` to a number of seconds; indexes unused for that long are dropped from memory and reloaded from the disk cache on the next search.

### Instructions (AGENTS.md / CLAUDE.md)

Add the snippet below to your `AGENTS.md` or `CLAUDE.md` so your agent knows when and how to call the semble CLI:

```markdown
## Code Search

Use `semble search` to find code by describing what it does or naming a symbol/identifier, instead of grep:

​```bash
semble search "authentication flow" ./my-project --max-snippet-lines 10  # first 10 lines only, concise
semble search "save_pretrained" ./my-project                          # full chunk content
semble search "save model to disk" ./my-project --top-k 10           # more results
​```

The index is built on first run (and cached for subsequent runs) and invalidated automatically when files change.

Use `--content docs` to search documentation and prose, `--content config` for config files (yaml, toml, etc.), or `--content all` to search code, docs, and config:

​```bash
semble search "deployment guide" ./my-project --content docs
semble search "database host port" ./my-project --content config
semble search "authentication" ./my-project --content all
​```

Use `semble find-related` to discover code similar to a known location (pass `file_path` and `line` from a prior search result):

​```bash
semble find-related src/auth.py 42 ./my-project
​```

`path` defaults to the current directory when omitted; git URLs are accepted. Pass several paths or URLs to search related repos together, e.g. when this project calls a service defined in a sibling repo. Result paths are then prefixed with the repo name and the output includes a `repos` map from prefix to location:

```bash
semble search "invoice endpoint" ./service-a ../service-b
```

If `semble` is not on `$PATH`, use `uvx --from "semble[mcp]" semble` in its place.

### Workflow

1. Start with `semble search` to find relevant chunks. The index is built and cached automatically.
2. Use `--content docs` for documentation, `--content config` for config files, or `--content all` for everything.
3. Navigate directly to the returned file and line — do not re-search or grep for the same content.
4. Optionally use `semble find-related` with a promising result's `file_path` and `line` to discover related implementations.
5. If the answer may live in a dependent or sibling repo, pass all relevant repo paths to one `semble search` call.
6. Use grep only when you need every occurrence of a literal string across the whole repo (e.g., all callers of a renamed function).
```

### Sub-agent

For harnesses that support sub-agents (Claude Code, Cursor, Gemini CLI, Kiro, OpenCode, GitHub Copilot, Codex, Reasonix, Pi, Command Code, Antigravity, ZCode, Grok Build, Qwen Code, Kilo Code), you can install a dedicated `semble-search` sub-agent. Copy the appropriate file from [`src/semble/agents/`](../src/semble/agents/) to your agent's agents directory:

> **Pi prerequisite:** Pi sub-agents require the Pi agents extension. Run `pi install npm:pi-agents` once before installing.

| Agent | File | Destination |
|---|---|---|
| Claude Code | `claude.md` | `~/.claude/agents/semble-search.md` |
| Cursor | `cursor.md` | `~/.cursor/agents/semble-search.md` |
| Gemini CLI | `gemini.md` | `~/.gemini/agents/semble-search.md` |
| Kiro | `kiro.md` | `~/.kiro/agents/semble-search.md` |
| OpenCode | `opencode.md` | `~/.config/opencode/agents/semble-search.md` |
| GitHub Copilot | `copilot.md` | `~/.copilot/agents/semble-search.agent.md` |
| Codex | `codex.toml` | `~/.codex/agents/semble-search.toml` |
| Reasonix | `reasonix.md` | `~/.reasonix/skills/semble-search.md` |
| Pi | `pi.md` | `~/.pi/agents/semble-search.md` |
| Command Code | `commandcode.md` | `~/.commandcode/agents/semble-search.md` |
| Antigravity | `antigravity.md` | `~/.gemini/config/skills/semble-search/SKILL.md` |
| ZCode | `zcode.md` | `~/.zcode/agents/semble-search.md` |
| Grok Build | `grok.md` | `~/.grok/agents/semble-search.md` |
| Qwen Code | `qwen.md` | `~/.qwen/agents/semble-search.md` |
| Kilo Code | `kilo.md` | `~/.config/kilo/agents/semble-search.md` |

---

--- METRICS ---
- Files Extracted: 3
- Estimated Token Budget: ~11560 tokens
- Canonical Reference: https://codewiki.google/github.com/MinishLab/semble
