Skip to main content

Query-adaptive context assembly for code retrieval: the score distribution decides how much context a query gets, within a token budget.

Project description

tobler: isoline contours around a query point

tobler

Query-adaptive context assembly for code retrieval. Tobler's first law for your context window: near things are more related than distant things, so let the score distribution decide how much context a query deserves, instead of a fixed top-5.

If you drive an AI coding agent, tobler feeds it the right code in fewer tokens, and on large repos it finds the target chunk where a fixed top-5 misses it two-thirds of the time. It sits on top of semble retrieval and ships as a library and a FastMCP server.

Requirements: Python ≥ 3.12 and an MCP client (e.g. Claude Code). semble is pulled in automatically (pinned). Published on PyPI as tobler-search (the import name stays tobler).

What it does in practice

  • Sharp query ("what does BACKOFF_BASE_MS do") → N_eff = 3, a few full bodies + a map, ε honest.
  • Diffuse query ("how does real-time collaboration work") → N_eff = 550, wide map of signatures and stubs.
  • Same token budget both times. Fixed top-k is wrong in both directions on the same repo.

Benchmark: hit rate (ground-truth symbol queries, ~40 per repo)

τ was calibrated once on two repos and never touched again; the first three rows are repos it never saw:

repo semble top-5 tobler B=1000 tobler B=2000
numpy (16,465 chunks) 32% 88% 92%
ripgrep (Rust) 48% 92% 95%
zod (TypeScript) 80% 95% 98%
8 smaller repos (247 queries) 76% 99% 100%
calibration repos (80 q, not held out) 94% 98% 99%

Fixed top-5 degrades hard as repos grow: on numpy it finds the right chunk a third of the time. Tobler holds ≥88% everywhere with zero per-repo tuning.

Benchmark: tokens (mean per query)

repo semble top-5 tobler B=1000 tobler B=2000
numpy 1463 995 1940
ripgrep 1544 993 1944
zod 1453 981 1940
8 smaller repos 1395 992 1966

Semble's spend is uncontrolled (whatever five chunks happen to cost, observed range ~1,000–2,800/query); tobler's budget is a hard ceiling that was never exceeded on any of ~370 benchmark queries. Tokens per correct answer (mean tokens ÷ hit rate): on numpy semble costs ~4,570 tokens per hit vs tobler's ~1,130 at B=1000, so it is 4× more token-efficient; ~3× on ripgrep, ~1.8× on small repos. When the budget genuinely can't cover the relevant code, ε reports it instead of silently truncating.

The math (seven equations, one calibrated knob)

  1. Center/standardize raw cosine scores over all N chunks: s̃ = (s − s̄)/σ, which removes the "everything resembles everything" anisotropy baseline.
  2. Softmax at temperature τ: ŵᵢ = softmax(s̃/τ).
  3. Effective count N_eff = 1/Σŵᵢ²: how many chunks genuinely matter for this query.
  4. Nucleus cut: smallest set S with mass ≥ p (default 0.9), so k adapts per query.
  5. Residual tail ε = 1 − Σ_S ŵᵢ, returned to the caller so the agent knows when it's reasoning inside a truncation.
  6. Budget allocation bᵢ = B·ŵᵢ/Σ_S ŵⱼ, mapped to level-of-detail tiers (full body → signature+docstring → path+symbol), with greedy repair rolling unspent budget down by weight.
  7. Session smoothing: m_t = λ_t·m_{t−1} + (1−λ_t)·s̃_t with λ_t = λ_max·max(0, corr(s̃_t, m_{t−1})). An anaphoric follow-up ("what are the arguments to that") gets pulled toward the session's focus; a topic pivot has corr ≈ 0, so memory vanishes with no reset heuristic. The gate experiment calibrated λ_max* = 1.0. The knob removed itself: the correlation is the blend. Follow-up hit rate 9% → 46%, pivots unharmed at 96% (see experiments/RESULT.md Addendum 3).

τ is the one calibrated free parameter (found by maximizing var(log N_eff) across real queries); p, k_min, and the budget B are fixed defaults you can override. On two real repos, τ* = 1.28 on the hybrid channel and N_eff spans two orders of magnitude (p90/p10 = 215×); see experiments/RESULT.md.

Why the scores are hybrid

Scores come from a hybrid channel (z-scored model2vec cosine blended with z-scored raw BM25) because static embedders can't see rare identifiers (mulberry32 ranks ~200th semantically, 3rd lexically). A k_min=5 floor on the nucleus cut guarantees coverage never drops below the old fixed top-5. Warm-index latency: numpy 54 ms/query, ripgrep 15 ms, zod 14 ms.

Quickstart

Install:

pip install tobler-search        # or: uv pip install tobler-search

Register the MCP server with Claude Code (uvx needs no separate install):

claude mcp add --scope user tobler -- uvx --from tobler-search tobler-mcp

Tool: search(query, repo, budget=2000, content="code", fresh=False) → markdown with an honesty header (N_eff=… k=… ε=… tokens=…/…, plus mem=… when session memory shaped the result) followed by tiered context blocks. Related follow-up queries are smoothed toward the session's focus; pass fresh=true to drop the memory for a query.

Run the server directly (stdio) with tobler-mcp.

CLI

pip install tobler-search puts the tobler command on your PATH. For shells, scripts, or agents without MCP access, the same search runs from the command line:

tobler index ./repo                          # warm the semble index first (optional)
tobler search "authentication flow" ./repo   # print the adaptive context
tobler search "save model to disk" ./repo --budget 3000
tobler search "deployment guide" ./repo --content docs

Output is the same honesty header + tiered blocks the MCP tool returns. (Session smoothing is MCP-only; the CLI is stateless per call.)

Agent skill

skills/tobler-search/ ships an agent skill that teaches a coding agent when and how to reach for tobler (MCP first, CLI fallback) and how to read the N_eff / k / ε / mem header. Point your agent's skill loader at it, or copy it into your skills directory.

Reproduce the benchmarks

From a source checkout (git clone https://github.com/datacompose/tobler):

uv run pytest                                      # 322 tests
uv run experiments/neff_sweep.py <repo1> <repo2>   # the τ calibration
uv run experiments/benchmark.py <repo1> [...]      # hit rate + tokens vs semble
uv run experiments/session_sweep.py <repo1> [...]  # the session-smoothing gate

Layout

  • src/tobler/core.py: the pipeline math, pure numpy
  • src/tobler/bridge.py: the only module touching semble private attrs (pinned semble==0.3.4)
  • src/tobler/render.py: tier rendering + token cost estimates
  • src/tobler/assemble.py: steps 1–7 composed; Session holds the smoothing memory
  • src/tobler/mcp.py: FastMCP server, one session stream per (repo, content)
  • src/tobler/cli.py: the tobler command (search + index)
  • skills/tobler-search/: shippable agent skill wrapping the MCP tool + CLI
  • experiments/: the validation gates (Phase 0, hybrid benchmark, session smoothing: all passed)

License

MIT. See LICENSE.


Built in a night, but the numbers aren't hand-wavy: τ calibrated once on held-out repos, every claim gated by an experiment in experiments/. Kick the tires and file issues.

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

tobler_search-0.1.0.tar.gz (150.8 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

tobler_search-0.1.0-py3-none-any.whl (15.7 kB view details)

Uploaded Python 3

File details

Details for the file tobler_search-0.1.0.tar.gz.

File metadata

  • Download URL: tobler_search-0.1.0.tar.gz
  • Upload date:
  • Size: 150.8 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.12.3

File hashes

Hashes for tobler_search-0.1.0.tar.gz
Algorithm Hash digest
SHA256 973b512da1bccdc4f518d13cef99ee69ccb13f780dd7638c0d79e33e08053ba6
MD5 4e5dfeccd79823fd1a5c3a0e13204e0f
BLAKE2b-256 01dca4eb0a2303e4ce61c91006a382559890bfad0cc821b811289edfb54c1bed

See more details on using hashes here.

File details

Details for the file tobler_search-0.1.0-py3-none-any.whl.

File metadata

  • Download URL: tobler_search-0.1.0-py3-none-any.whl
  • Upload date:
  • Size: 15.7 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.12.3

File hashes

Hashes for tobler_search-0.1.0-py3-none-any.whl
Algorithm Hash digest
SHA256 e78d9a97911e191266d404b78fc1d021eb11c80ec66f9451b21b94f978b5c4e2
MD5 a3cba3ef7d00d238bd48aed56bd7f5b4
BLAKE2b-256 8ef6ddbc144e5982f0186d9188f94b228141f586dabb9607932810d328b7ae9f

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page