Use when building, extending, or reviewing tensor-grep's APPROVED local hybrid semantic search — BM25 + CPU dense embeddings fused with Reciprocal Rank Fusion (RRF), no API key, no GPU (roadmap item #1). Load before adding a dense/embedding leg, RRF fusion, a `tg index` command, or changing `tg search --rank` / `--bm25`. Covers the decision-gated build phases with exact commands + expected gate numbers, the ranked solution menu (Semble / ripvec / BM25-only) with derivation obligations, the retrieval-quality + editor-plane + token-economy promotion gates, the Backend Fail-Closed Contract for the dense leg, fenced-off wrong paths (no API-key embeddings, no GPU dependency, do not break `--format rg` / `--json` / `--ndjson` semantics), and routing promotion through change-control. STATUS as of 2026-07-08, v1.49.3: the dense leg + RRF fusion described here as the target architecture (Candidate 1 / the Semble pattern) SHIPPED as `tg search --semantic` (`retrieval_dense.py` + `retrieval_fusion.py`, default-OFF, gate
يبقى الأمر في سطر واحد. مرّر أفقيًا لمراجعته كاملًا قبل النسخ.
تفضّل نسخة محلية؟ نزّل الملفات المتاحة حاليًا لدى SkillsMP.
عرض SKILL.md
SKILL.md
تعليمات المصدر · معاينة للقراءة فقط
name
tensor-grep-semantic-search-campaign
description
Use when building, extending, or reviewing tensor-grep's APPROVED local hybrid semantic search — BM25 + CPU dense embeddings fused with Reciprocal Rank Fusion (RRF), no API key, no GPU (roadmap item #1). Load before adding a dense/embedding leg, RRF fusion, a `tg index` command, or changing `tg search --rank` / `--bm25`. Covers the decision-gated build phases with exact commands + expected gate numbers, the ranked solution menu (Semble / ripvec / BM25-only) with derivation obligations, the retrieval-quality + editor-plane + token-economy promotion gates, the Backend Fail-Closed Contract for the dense leg, fenced-off wrong paths (no API-key embeddings, no GPU dependency, do not break `--format rg` / `--json` / `--ndjson` semantics), and routing promotion through change-control. STATUS as of 2026-07-08, v1.49.3: the dense leg + RRF fusion described here as the target architecture (Candidate 1 / the Semble pattern) SHIPPED as `tg search --semantic` (`retrieval_dense.py` + `retrieval_fusion.py`, default-OFF, gated on the `semantic` extra) — this skill's Phases 0-3 are now historical/reference for HOW it was built; see the STATUS note below before assuming any "not built yet" claim in Sections 1-2.
tensor-grep — Local Hybrid Semantic Search Campaign
A decision-gated runbook for building the APPROVED local hybrid semantic search
layer: BM25 (lexical) + a CPU dense-embedding leg, fused with Reciprocal Rank
Fusion (RRF), 100% local, no API key, no GPU. This is roadmap item #1
(grep -n "the #1 validated user ask" AGENTS.md; was :561-562 at v1.96.0, now :1738 at v1.101.27 — cite the grep, not the number) — the #1 validated user
ask and the biggest competitive gap.
This skill is the campaign map. It tells you what already exists, what you are
building, the exact commands + expected numbers at each gate, the wrong paths that
are fenced off, and how promotion routes through change-control. You do not ship
anything user-visible from this skill without beating the gate and doing a conscious
flag-flip (see Phase 5).
STATUS UPDATE (2026-07-08, v1.49.3): SHIPPED. The dense leg + RRF fusion this
skill specifies as the target architecture (Candidate 1, the Semble pattern) is now
live: src/tensor_grep/core/retrieval_dense.py (model2vec + potion-code-16M,
DenseUnavailableError/BackendExecutionError fail-closed contract exactly as §6
specifies) and src/tensor_grep/core/retrieval_fusion.py
(reciprocal_rank_fusion(rankings, k=DEFAULT_K=60) — matches §3/§5 exactly), wired
as tg search --semantic (grep -n '"--semantic"' src/tensor_grep/cli/main.py; was main.py:7069, now main.py:7331 typer option, default False; bootstrap
front doors at bootstrap.py:70 [_TG_ONLY_SEARCH_FLAGS, keeps it off the
rg-passthrough] and bootstrap.py:478 [_can_delegate_to_native_tg_search's
unsupported_flags, keeps it off native-Rust delegation]) gated on the optional
semantic extra (pyproject.toml:627, model2vec>=0.5+numpy>=1.26; all four
citations re-verified 2026-07-24 against v1.96.0). Sections 1-2 below still
describe the PRE-BUILD state and are now WRONG on the "does not exist yet" claims —
read them as historical design intent, not current fact. No tg index command
was added (the persisted-index building blocks in semantic_index.py remain
unwired, per the original §1 note). RESOLVED (2026-07-24, re-verified against
v1.96.0): --semantic has NOT graduated past default-OFF —
SearchConfig.semantic_rank: bool = False (config.py:188) and AGENTS.md's own
Roadmap Sequencing section still describes the shipped win as "default-OFF"
(grep -n "the #1 validated user ask" AGENTS.md; was :561-562, now :1738). Still not re-verified: whether Phase 4's promotion gate
(RRF-hybrid beats BM25-only on a real corpus + editor-plane latency) was actually
measured before the ORIGINAL ship — that is a historical-PR question this pass did
not chase down; if you need it, check the --semantic flag's introducing PR for
attached benchmark evidence. If you are extending this further (chunking, a
tg index command, a default-flip), Phases 4-5 below are still the right runbook.
STATUS UPDATE 2 (2026-07-16, v1.77.0-v1.78.1, campaign #189): the architecture
GRADUATED into a standalone whole-repo command, tg find. Where --semantic
re-ranks an EXISTING regex match set, tg find walks and ranks the WHOLE repo (no
pattern pre-filter), reusing the same retrieval_dense.py/retrieval_fusion.py
core. It shipped its own golden harness, benchmarks/eval_late_rerank_quality.py
(a 40-query NL vocab-mismatch golden set + literal/identifier golden slices,
superseding this skill's toy eval_bm25_quality.py as the Phase-4-style
discriminating gate for tg find specifically) — gate-run result: rrf beats
bm25 by +0.195 ndcg@10 / +0.30 recall@10, bidirectional-oracle-validated
(internal; public numbers stay CEO-gated #72). Two related dispositions are:
(1) MaxSim late rerank (TG_LATE_RERANK)
is RETIRED (task F10): the post-role-aware-encoder measurement was decisively negative;
retrieval_late.py records the retirement. Do not re-run the same encoder expecting a
different verdict. (2) TG_FIND_DENSE_WEIGHT (see tensor-grep-config-and-flags) now
adaptively applies 5.0 to multi-word queries when unset, empty, malformed, or non-finite;
single-token queries remain 1.0, and explicit TG_FIND_DENSE_WEIGHT=1.0 opts out.
Receipt (real-corpus-dogfood-beats-fixture-green): the query
classifier that scopes TG_FIND_DENSE_WEIGHT to multi-word queries was originally a
split_terms() morpheme-count floor (> 2 morphemes = NL); it passed its synthetic
literal-golden fixture but a real-repo dogfood on tensor-grep's own src/ caught it
mis-boosting 5 of 6 literal identifier queries (_confine_mcp_path, getUserName,
reciprocal_rank_fusion all split into 3+ morphemes) — fixed by switching to a
whitespace word-count gate (len(query.split()) <= 1 stays literal), #191/#630.
See tensor-grep-run-and-operate §1/§7/§11c for the CLI/MCP command surface and
exit contract, and the dedicated operator skill tensor-grep-find-and-route for the
day-to-day tg find/tg route-test CUJ (this skill stays the BUILD/campaign history; that one is
the how-to-run doc).
0. When to use this skill — and when to use a sibling instead
Use this skill when the task is the hybrid-retrieval build itself: adding a dense
leg, RRF fusion, a persisted hybrid index, a tg index command, or measurably
improving tg search --rank.
If you actually need to…
Use this sibling instead
Understand the front door / routing / registration sites / backend contract
tensor-grep-architecture-contract
Get merge/release/experimental-flag gates + the incidents behind them
Learn a settled battle (FFI reverts, dep caps, mock-vs-real) so you don't re-fight it
tensor-grep-failure-archaeology
Build/run the toolchain (uv, maturin, cargo)
tensor-grep-build-and-env
The local validation gate + QA
tensor-grep-validation-and-qa
Update README/AGENTS/docs after shipping
tensor-grep-docs-and-writing
Position the feature externally (never "faster grep")
tensor-grep-release-and-positioning
This skill never routes around change-control. Promotion is a change-control
decision (Phase 5); this skill only produces the evidence that decision needs.
1. What already exists (the BM25 lexical leg is SHIPPED)
2026-07-08 correction: this section (and §2 below) was written when only the BM25
leg existed. The dense leg (retrieval_dense.py) and RRF fusion
(retrieval_fusion.py) have SHIPPED since — see the STATUS box above. Treat every
"does not exist yet" statement below as describing the pre-2026-07-0x state, not
current fact; re-verify with the grep in the STATUS box before relying on it.
Read these before writing a line. Every path below is verified against the repo as
of v1.17.25.
File
What it does
Load-bearing facts
src/tensor_grep/core/retrieval_chunker.py
Splits files into line-window chunks
chunk_file(chunk_size=30, overlap=5); step = max(1, chunk_size-overlap) = 25; MAX_CHUNKS=100_000 loud guard (raises, never silent OOM). Per-chunk granularity is what the design council settled on (not per-line, not per-file).
src/tensor_grep/core/retrieval_lexical.py
Tokenizer + bare overlap counter
split_terms() is camelCase/underscore/hyphen aware, lowercased. This is the shared tokenizer — the dense leg MUST tokenize identically or scores diverge.
src/tensor_grep/core/retrieval_bm25.py
Okapi BM25 over chunks
Bm25Index, k1=1.5, b=0.75, IDF with +1 smoothing (non-negative weights). Dedupes query terms so a repeated token isn't double-counted. Returns [(chunk_index, score)], zero-score chunks excluded, ties break by chunk index (deterministic).
src/tensor_grep/core/reranker.py
The LIVE tg search --rank path
rerank_by_bm25(result, query, file_paths) re-orders matches by the best BM25 score of the chunk containing each match; stable sort (ties keep grep order); non-scoring matches sink. Builds the BM25 index in memory every call over just the matched files — no persisted index.
src/tensor_grep/core/semantic_index.py
Persisted chunk-BM25 index building blocks
build_and_save / load_or_warn under .tg_semantic_index/ (env TG_SEMANTIC_INDEX_DIR), SEPARATE from the Rust TGI v3 .tg_index (trigram). INDEX_VERSION=2 (re-verified 2026-07-24 against v1.96.0; bumped from 1 when the structural chunker folded its mode into the cache key, per the "Two further opt-in refinements" note above). Stale check = SHA-256 fingerprint over sorted paths + mtimes → on mismatch, warn to stderr + return None → in-memory fallback. NOT wired to the CLI — there is no tg index command yet (still confirmed absent).
(CLI command, v1.91.0)
How --rank is wired (verify before changing):
Flag: --rank (alias --bm25), default OFF. SearchConfig.rank_bm25 = False (config.py:183, re-verified 2026-07-24 against v1.96.0). The dense leg's own flag sits right below it: SearchConfig.semantic_rank = False (config.py:188).
It is a TG-only search flag: bootstrap.py::_TG_ONLY_SEARCH_FLAGS (--rank line 68, --bm25 line 69, --semantic line 70 — re-verified 2026-07-24) — the bootstrap front door intercepts it and does NOT forward it to ripgrep. This is one of the two flag front doors; see tensor-grep-config-and-flags.
Setting --rankleaves the ripgrep passthrough fast-path: the _can_passthrough_rg() condition includes and not config.rank_bm25 and and not config.semantic_rank (grep -n "not config.rank_bm25\|not config.semantic_rank" src/tensor_grep/cli/main.py; was main.py:5232-5250 at v1.96.0, now main.py:5438-5463 at v1.101.27 — this seam has already drifted twice inside two weeks, cite the grep, not the number), so the request runs the tg engine and results are re-ordered right after match aggregation — the elif config.rank_bm25 and all_results.matches: guard through the rerank_by_bm25(...) call (grep -n "elif config.rank_bm25 and all_results.matches\|rerank_by_bm25" src/tensor_grep/cli/main.py; was main.py:7965-8069; the follow-up :8411-8414 pin then landed INSIDE a --deadline option block deleted by the 2026-08-23 de-duplication, so it has no successor either -- the THIRD drift of this one anchor. Use the grep above and stop pinning it).
User docs: grep -n -- "--rank" README.md (feature bullet :39, example :172-173 as of 2026-08-14; were :38 and :147-148 at the v1.96.0 pass).
Bottom line: the lexical leg (BM25) and the persisted-index building blocks
already exist and ship default-OFF. The campaign adds the **dense leg + RRF fusion
(optionally) a wired persisted hybrid index.**
2. What you are building (the approved architecture)
chunk_file() ──► chunks ──► ┌─ BM25Index.query() ──► ranking A
query ──► split_terms() │
(same tokenizer) └─ dense encode+cosine ──► ranking B
│
RRF fuse(A, B, k=60) ──► final ranking
BM25 leg — exists (retrieval_bm25.py).
Dense leg — DOES NOT EXIST YET. A CPU static-embedding model produces a vector
per chunk and per query; rank chunks by cosine similarity. Static means a per-token
vector lookup (no transformer forward pass at query time) → fast on CPU, no GPU,
no API key, no network at query time.
RRF fusion — DOES NOT EXIST YET. Combine the two rankings without score
normalization: score(d) = Σ_r 1 / (k + rank_r(d)) over the rankers r ∈ {bm25, dense}, with k = 60 (the value the reference architecture uses). A document
absent from a ranker's list contributes 0 for that ranker. RRF is rank-based, so it
is robust to the fact that BM25 scores and cosine scores are on incomparable scales.
SUPERSEDED (was true through v1.40.2, 2026-07-05; false as of v1.49.3, 2026-07-08):no dense/embedding/RRF/Model2Vec/potion code exists in src/ today — this leg has
since shipped as retrieval_dense.py + retrieval_fusion.py; re-run
grep -rin "model2vec|potion|reciprocal_rank_fusion|StaticModel" src/ yourself and
expect real hits, not just comments.
The moat framing (do not lose it): this is not "faster grep." ripgrep is the
raw-text parity baseline. The value is agent-native retrieval quality on
vocabulary-mismatch queries (find authenticate when the user typed "verify login").
Keep the positioning honest per tensor-grep-release-and-positioning.
3. Solution menu (RANKED) with derivation obligations
Pick in this order. Each candidate carries a derivation obligation — a claim you
MUST verify (not assume) before you build on it. "Derive" = confirm against a
primary source (the model card, the license file, a local import test), then record
the finding. Route the research through tensor-grep-research-frontier +
tensor-grep-research-methodology; never trust a self-report (change-control gate B).
Candidate 1 (preferred): the Semble pattern
Tree-sitter chunking + potion-code-16M Model2Vec static embeddings + BM25 + RRF
(k=60). CPU-only, MIT. This is the reference architecture named in AGENTS.md
(grep -n "MinishLab .Semble." AGENTS.md; was :565 at v1.96.0, now :1741 at v1.101.27; the
"## Roadmap Sequencing" heading it sits under was :525, now :1701 — AGENTS.md keeps growing new
sections above these, cite the grep not the number).
Derivation obligations before you depend on it:
License — confirm potion-code-16M (and the model2vec runtime) are
MIT/Apache-compatible with tensor-grep's Apache-2.0 and add the required NOTICE
entries. Ideas are free; imported code/weights need their notices.
Truly offline — confirm the model loads from a bundled/cached file with no
network call and no API key at query time. If it phones home or needs a token,
it is DISQUALIFIED (see §4 fenced paths).
CPU + footprint — confirm it runs with no GPU, and record the on-disk model
size and the added dependency weight. The model must be an OPTIONAL extra, not
a hard install dependency (every-install must still work with BM25-only).
Chunking choice -- RESOLVED, both ship. Semble uses tree-sitter chunks;
tensor-grep already had line-window chunk_file as the default, and now also ships
TG_CHUNKER=structural (PR #443, v1.47.0) -- opt-in cAST AST-shaped chunking
(docs/PAPER.md/arXiv:2506.15655 is the reference paper), fail-open and
chunk-shape-identical to the fixed-window path when unset. This was a candidate
deepener as of the original design; it is now a shipped, default-off refinement --
do not describe it as unbuilt.
Candidate 2: ripvec (pure-Rust)
A pure-Rust vector path. Derivation obligations: confirm license, maturity, and
whether it fits the existing PyO3 bridge without reintroducing the FFI overhead that
was already measured too high and reverted (tensor-grep-failure-archaeology: FFI is
not the dir-scan speed path). Only choose this if Candidate 1 fails a gate AND you
have measured that the Rust path clears the same promotion bar.
Candidate 3: BM25-only (the honest null result)
Ship nothing new. This is a legitimate, non-embarrassing outcome if the dense leg
does not beat the BM25 baseline on both retrieval quality and editor-plane latency.
"No speed/quality claim without measured numbers vs the baseline" (change-control
gate C) cuts both ways: if the numbers aren't there, the correct move is to keep the
shipped --rank baseline and record the negative result. grep -n -- "demonstrably beats" README.md -- :237 as of 2026-08-14 (was :212 at the v1.96.0 pass)
states the rule explicitly: extend lexical
(BM25) re-ranking with AST-shaped chunking or semantic re-ranking only when it
demonstrably beats the shipped tg search --rank baseline on both retrieval quality
and editor-plane benchmarks.
4. Fenced-off wrong paths (do NOT do these)
Forbidden
Why
If you're tempted
API-key / hosted embeddings (OpenAI, Voyage, Cohere, any *_API_KEY)
Breaks "no API key, runs on every install, local-first." The whole point is $0, offline.
Static local model only. If a candidate needs a key or a network call at query time, it's disqualified.
GPU / CUDA dependency for the dense leg
GPU is EXPERIMENTAL, default-OFF, and currently slower than CPU with no promotion-ready path (Roadmap Sequencing Phase 1, "reversible flag-flip, not yet authorized" — no crossover proven, grep -n "reversible flag-flip, not yet authorized" AGENTS.md; was :539-541 at v1.96.0, now :1715 at v1.101.27). A GPU-gated ranking layer would not run on the common install.
CPU static embeddings. GPU may be an optional future accelerator, never a requirement.
Those output contracts are the raw-grep parity surface. --rank is a re-order overlay: same matches, different order. When --rank is NOT set, the ripgrep passthrough fast-path (main.py:5212, _can_passthrough_rg, re-verified 2026-07-24) must remain byte-for-byte.
Keep ranking strictly post-processing over an already-produced SearchResult. Never change match membership or the rg-shaped output when ranking is off.
A hard new install dependency
Every-install must keep working.
Make the dense model an optional extra; degrade to BM25-only when absent (see §6).
Default-OFF flag + benchmark + conscious flag-flip (Phase 5).
Eyeballing "it feels more relevant"
Ranking surfaces silently FLIP on corpus change; the blast radius is invisible to the call graph (known weak point — flat scorer, incident #302).
Measure recall@k / ndcg@k on a real corpus. Numbers or it didn't happen.
Routing around change-control
Non-negotiable.
5. The phased runbook (decision-gated)
Run phases in order. Each gate says the expected number and where to branch if
you see something else. All commands are copy-pasteable; PowerShell is the primary
shell on the dev box, but uv run is cross-platform.
uv run gotcha: a bare uv run ... can re-sync and drop the [dev]
tree-sitter/extras tree. For benchmark/import work use uv run --no-sync ... so the
installed dev tree (and any editable install of the dense model) is not wiped. See
tensor-grep-build-and-env.
Phase 0 — Establish the baseline (NEVER skip)
You cannot claim an improvement without the number you improved on.
uv run --no-sync python benchmarks/eval_bm25_quality.py --top-k 3
benchmarks/eval_bm25_quality.py defines V2_GATE_RECALL = 0.60 and states in its
own docstring: "the v2 dense+RRF leg must beat this before it ships user-visible."
GATE 0a: recall@k must be 1.000 here. If it is lower, the BM25 leg itself
regressed → STOP, do not build dense on a broken base → tensor-grep-debugging-playbook.
GATE 0b — read this or you'll waste weeks: this toy corpus is
keyword-discriminating, so BM25 already saturates it at recall 1.0. Passing the
0.60 gate proves nothing about whether dense helps — it is a floor / sanity
gate, not the discriminating gate. The real justification for a dense leg is
vocabulary-mismatch queries (synonyms/paraphrase) where lexical BM25 misses.
Your promotion evidence MUST come from a harder, realistic corpus (Phase 4), not
this file.
Phase 1 — Choose + derive the solution
Work the §3 menu top-down. Complete every derivation obligation for your chosen
candidate and write the findings down (license, offline-proof, CPU-proof,
footprint, optional-extra plan). Research via tensor-grep-research-frontier +
tensor-grep-research-methodology. Before writing code, verify the plan's seam
claims against the real files with file:line citations (verify-plan-against-code) —
an AI-drafted plan that says "add it in pipeline.py" is a hypothesis until you
confirm --rank is actually wired in main.py.
GATE 1: if the preferred candidate fails a derivation obligation (needs a key,
needs a GPU, incompatible license, cannot be an optional extra), do not "work around
it" — drop to the next candidate. If all dense candidates fail, Candidate 3
(BM25-only) is the correct answer; document the negative result and stop.
Phase 2 — Build the dense leg behind a default-OFF experimental flag
Add a new module (mirror the existing seam names, e.g.
core/retrieval_dense.py) — do not bolt onto retrieval_bm25.py.
Reuse split_terms() (or an explicitly justified tokenizer) so the two legs stay
comparable.
Wire it behind a separate default-OFF flag (the config.py:182 note already
anticipates "a separate gated flag" for the dense leg). Do NOT change the meaning of
--rank/--bm25 yet.
Honor the Backend Fail-Closed Contract (§6).
TDD: write the contract test first (tests/unit/test_retrieval_dense.py), then the
smallest implementation. See existing tests tests/unit/test_retrieval_bm25.py,
tests/unit/test_reranker.py, tests/unit/test_semantic_index.py for the pattern.
GATE 2: the dense leg imports and runs with the model absent (degrades to
BM25-only, visibly) AND with it present (produces a ranking). Prove the FFI/model
path against the REAL runtime, not a mock — mock-green while the real bridge is dead
is a documented trap (tensor-grep-failure-archaeology).
Phase 3 — Build RRF fusion
Implement reciprocal_rank_fusion(rankings, k=60) as a pure function taking each
leg's ordered list of chunk indices and returning the fused order. Keep k
configurable (default 60) and deterministic ties.
Fuse ranks, not raw scores (BM25 score vs cosine are incomparable scales — this
is the whole reason RRF is chosen).
GATE 3: unit-test that fusing two identical rankings is a no-op, and that a
document top-ranked by either leg surfaces near the top of the fused list.
Phase 4 — Measure (the real gate)
Two measurements, both required (grep -n -- "demonstrably beats" README.md -- :237 as of 2026-08-14, was :212):
Retrieval quality on a realistic corpus (not the toy). Use
benchmarks/eval_late_rerank_quality.py — the LIVE, chunker/ranking-sensitive harness (it actually
imports and calls chunk_file/rank_chunks, and computes RetrievalMetrics: recall/precision/
mrr/ndcg on a real repo + the 40-query NL golden set). Do not use
benchmarks/run_repo_retrieval_benchmarks.py for this — it is a static-fixture REPLAY that never
calls chunk_file and cannot detect a chunker or dense/RRF-weighting change at all (this was the
cAST-chunking evaluation's own harness-selection mistake before STATUS UPDATE 3 above corrected
it). Produce three rows: BM25-only, dense-only, RRF-hybrid, on the SAME corpus + queries.
uv run --no-sync python benchmarks/eval_late_rerank_quality.py --output artifacts/bench_find_quality.json
(Read its args first; see tensor-grep-benchmark-and-proof-toolkit §7 for the corpus-hardness and
paired win/loss/tie reporting rigor this gate specifically needs.)
Editor-plane latency — the ranking overlay must not blow the interactive
budget:
uv run --no-sync python benchmarks/run_editor_plane_benchmarks.py
Route interpretation through tensor-grep-benchmark-and-proof-toolkit (noise-floor
rule for sub-10ms rows, fair-baseline rule, launcher attribution). Also record the
token-economy delta if the surface feeds the agent capsule (AGENTS.md names
token economy as a focus).
Promotion gate (all must hold, measured vs the SAME baseline run):
Metric
Requirement
recall@k (real corpus)
RRF-hybrid > BM25-only by a margin beyond the noise floor
ndcg@k (real corpus)
RRF-hybrid ≥ BM25-only (no ranking-quality regression)
Editor-plane latency
within the interactive budget; no material regression vs --rank
Token economy (if capsule-facing)
no worse than BM25-only
GATE 4: if RRF-hybrid does not beat BM25-only on retrieval quality AND hold the
line on latency, do not ship it. Reject the regression even if the code is clean
(change-control gate C). The honest outcomes are: (a) improve the dense leg/chunking
and re-measure, or (b) record the negative result and keep BM25-only.
Phase 5 — Promote through change-control (never here)
This skill produces evidence; tensor-grep-change-control owns the flip. The
graduation path is fixed:
Ship experimental, default-OFF (already true after Phase 2).
Attach the Phase 4 evidence (three-row quality table + editor-plane numbers +
token economy) to the PR.
Dry-run on real data (dogfood the REAL binary via scripts/dogfood/; CliRunner
bypasses the bootstrap front door and will not exercise routing).
Conscious flag-flip — a deliberate, reviewed default change, never auto-merged,
never admin-merged. Autonomy is draft-PR-only.
Update docs (README.md, AGENTS.md, the usage skill) via
tensor-grep-docs-and-writing; observe one-merge-per-tick + the push-race rules
(tensor-grep-release-and-positioning).
If you add a tg index command (the natural home for a persisted hybrid index),
remember it needs the 4 command-registration sites (miss one → silent misroute)
and a new flag needs the 2 flag front doors — see tensor-grep-config-and-flags
and tensor-grep-architecture-contract.
6. Backend Fail-Closed Contract for the dense leg
The dense leg is a compute path; it is bound by backends/base.py
(BackendExecutionError) and the AGENTS.md §"Backend Fail-Closed Contract"
contract (grep -n "Backend Fail-Closed Contract" AGENTS.md; was :496 at v1.96.0, now :1672 at
v1.101.27). The recurring anti-pattern to
avoid: a bare except Exception: that silently returns empty or swaps engines.
Model missing / not installed → this is a legitimate degraded fallback to
BM25-only, but it MUST be VISIBLE: set a fallback_reason on the SearchResult
so JSON/CLI consumers can tell degraded output from full hybrid output. Never label
BM25-only output as "semantic."
Model load/encode raises at runtime (corrupt cache, OOM, version skew) → raise
BackendExecutionError; do not return a clean empty result that reads as "no
matches." A real failure reported as a no-match is the exact bug this contract
exists to prevent.
Contract flag the fallback cannot honor → fail closed (raise), do not swap. (For
a ranking overlay a graceful visible degrade to BM25 is the norm; only fail-closed
if a caller explicitly demanded semantic-only and you cannot deliver it.)
Validate the model's output shape (vector dimensionality, chunk count) before you
index, so a mismatch degrades gracefully instead of raising an IndexError that a
broad except then swallows.
See tensor-grep-architecture-contract for the full contract and the
planned SafeBackendMixin conformance gate.
7. Common failure modes → branch
Symptom
Likely cause
Branch to
BM25 baseline recall < 1.0 in Phase 0
BM25/tokenizer/chunker regression
tensor-grep-debugging-playbook
Dense leg "works" in tests but the real binary shows no effect
mock-green while the real path is dead; or --rank not actually re-routing
tensor-grep-failure-archaeology; verify against the real binary via scripts/dogfood/
uv run benchmark can't import the dense model
uv run re-synced away the extra
re-run with uv run --no-sync; tensor-grep-build-and-env
Phase 4 evidence: three-row quality table (BM25 / dense / RRF) on a REAL corpus + editor-plane latency + token economy — RRF beats BM25-only on quality without a latency regression, OR the negative result is documented and you stop.
--json / --ndjson / --format rg unchanged when ranking is off.
Local validation green: uv run ruff check . · uv run ruff format --check --preview . · uv run mypy src/tensor_grep · uv run pytest -q (CI runs ruff format --check --preview — you MUST pass --preview).
Real-binary dogfood, not just CliRunner (scripts/dogfood/).
Promotion routed through tensor-grep-change-control; draft PR only; conscious flag-flip, never auto-merge.
Provenance and maintenance
Everything below is verifiable from the repo. Re-run these when a claim may have
drifted; date-stamp any change.
Version / date: facts originally verified v1.17.25 (2026-07-02); re-verified
UNCHANGED against released v1.40.2 (origin/main 8829441) on 2026-07-05; spot-checked
again 2026-07-08 against v1.49.3 and found the dense/RRF leg now SHIPPED (see STATUS
UPDATE near the top); spot-checked again 2026-07-16 against v1.78.1 and found the
architecture graduated into tg find (see STATUS UPDATE 2 near the top); spot-checked again
2026-07-22 against v1.93.2 and recorded the cAST-chunking rejection + dense-int8 deferral +
install-dense row (see STATUS UPDATE 3 near the top — this was targeted at the research-campaign
#251 retirements and the harness-selection correction, not a full re-walk of Phases 0-8 below).
**Spot-checked again 2026-07-24 against v1.96.0 (origin/main 29cf59f): deleted the
Provenance bullet below that directly contradicted the "Dense leg + RRF now shipped" bullet
(its own grep no longer produces the "expect no hits" result it claimed — 5 real hits in
main.py/config.py/reranker.py/retrieval_dense.py/retrieval_fusion.py); resolved the
open "has --semantic graduated past default-OFF" question (NO — see the STATUS UPDATE box
near the top); re-verified and fixed drifted file:line citations throughout §1/§3/§4/§6/
Provenance (main.py/bootstrap.py/pyproject.toml/README.md/AGENTS.md line numbers had
each drifted anywhere from ~10 to ~1500 lines since the v1.17.25-era pins — main.py's
_can_passthrough_rg moved the most, 3883→5249); fixed INDEX_VERSION=1 → the
now-current INDEX_VERSION=2; and corrected a stale STATUS UPDATE 2 claim (the
retrieval_late.py doc-role-encoder harness gap it cited as blocking TG_LATE_RERANK was
fixed by #189 Item 1 since that note was written; the later F10 measurement retired MaxSim).
This was a targeted re-verification of THIS skill's own claims, not a full re-walk
of every sibling skill or every historical receipt (e.g. the 2026-07-16 +0.195 ndcg@10
gate-run number in STATUS UPDATE 2 is a dated point-in-time receipt, left as-is).
Skill-library drift audit, 2026-08-01, against v1.101.27: every /
citation re-grepped again and every one had drifted since the 2026-07-24 pass —
's condition moved a SECOND time (->), the
call site moved ->, the typer option moved
->, and the "Roadmap Sequencing"/"Backend Fail-Closed Contract"
citations each moved ~1150-1180 lines (->, ->, ->,
->, ->). Every one of these was converted from a hard line
number to a instruction with the drift kept beside it as the
receipt, per AGENTS.md's own "cite the SYMBOL, not the line — and never re-stamp" rule — the
second time in THIS skill's own history (see the ->-> chain above) that a
line-number citation set has been caught stale on re-verification.
, , ,
, , , and
were all re-checked the same pass and found UNCHANGED — small, stable files
don't drift the way // do; that is a receipt, not an excuse to
stop re-checking them next time.
Re-check: and .
Open / candidate (not proven — do not present as fact):
The exact potion-code-16M license, offline behavior, size, and dep footprint are
derivation obligations (Phase 1), not settled facts — verify via
tensor-grep-research-frontier.
The k=60 RRF constant and line-vs-AST chunking are candidates; the final
values are whatever Phase 4 measurement supports.
Whether the dense leg beats BM25-only at all on a real corpus is unknown until
measured — Candidate 3 (ship nothing) remains a valid, honest outcome.
Two further opt-in refinements SHIPPED since (both default-OFF, additive):TG_CHUNKER=structural (PR #443, 9015238, shipped v1.47.0) -- cAST AST-shaped
chunking (retrieval_chunker.py, CHUNKER_MODE_ENV_VAR) beside the fixed-window
chunker, fail-open, chunk-shape-identical contract, index-version-bumped
(semantic_index.py v2 folds the active chunker mode into its cache key). This
retires the old "cAST is a candidate deepener, not a requirement for v1" framing in
S3 Candidate 1 below -- it shipped as an explicit opt-in, not a requirement, but it is
no longer merely a future candidate. TG_RRF_CHANNELS=1 (PR #442, a402f81, shipped
v1.46.0) -- channelized RRF (reranker.py, _RRF_CHANNELS_ENV): weighted per-channel
fusion including a 1.5x path/filename channel, additive weights param, default-off
and byte-identical when unset.
(Superseded note, kept for history: as of 2026-07-05/v1.40.2 this was still unbuilt
and tensor-grep-large-repo-scale-campaign was the live campaign instead — that is
no longer the case for the dense/RRF leg specifically; re-check which campaign is
"live" at the time you read this.)
STATUS UPDATE 3 (2026-07-21, research campaign #251) — cAST structural chunking REJECTED as
default; do not re-propose it.TG_CHUNKER=structural (shipped v1.47.0, mentioned above as an
opt-in refinement) was evaluated as a candidate for the DEFAULT chunker on a real-corpus retrieval
eval: the retrieval-quality delta was a net WASH, while cAST chunking ran 24.4x SLOWER and
produced ~38% LARGER chunks than the shipped line-window chunk_file. The opt-in code remains
shipped for experimentation; it is not promoted, and this is now a documented retirement in
docs/PAPER.md §3.10 — do not re-run this experiment expecting a different verdict without new
evidence. Use the right harness when re-measuring anything chunker-sensitive:benchmarks/eval_late_rerank_quality.py (live — imports and calls chunk_file/chunk_file_structural)
is the correct instrument; benchmarks/run_repo_retrieval_benchmarks.py is a static-fixture
REPLAY that never calls chunk_file at all — it cannot detect a chunker regression or improvement
and was mistakenly cited as the Phase-4 discriminating gate earlier in this skill (§0/Phase 4 below
now point at the correct script).
One-line caution (do not re-propose): dense-embedding compression (int8/binary/PCA) was
evaluated and DEFERRED — memory-only win (3.79x smaller on disk), but ~2x SLOWER in numpy (no
int8 SIMD on the CPU-only path this campaign targets); a real speed win needs a native kernel
(banked as a moat-investment option, not a quick follow-up here).
Sibling hardening, not a semantic-search change:#699/A7 hardened the SIBLING flat lexical
scorer (_score_symbol in repo_map.py — exact word-boundary bonus + test-file demotion), which
is a DIFFERENT scorer from this campaign's BM25/dense/RRF stack (see
code-search-and-retrieval-reference §3) — do not conflate the two when reading a "ranking fixed"
claim.
STATUS UPDATE 4 (2026-08-12 retention pass; verified against v1.110.14, base 568065a): the
TG_FIND_DENSE_WEIGHT claim in STATUS UPDATE 2 item (2) above — "default-OFF (1.0 =
byte-identical no-op)" with the flip "a separate, still-open CEO checkpoint" — is FALSE at this
SHA. The flip SHIPPED (#191/#634, commit c1d4ba4); the CEO checkpoint is closed by it.
Verified resolution rule (grep -n "_FIND_DENSE_WEIGHT_ADAPTIVE_DEFAULT" src/tensor_grep/cli/main.py — = 5.0 at main.py:4614; reader _find_dense_weight at
main.py:4627-4684): unset / empty / malformed / non-finite env → the adaptive
_FIND_DENSE_WEIGHT_ADAPTIVE_DEFAULT (5.0, the ledger-swept 1:5 bm25:dense ratio) for genuinely
multi-word NL queries; a single whitespace-free token stays pinned at _FIND_DENSE_WEIGHT_DEFAULT
(1.0) regardless of the env var's state; explicit TG_FIND_DENSE_WEIGHT=1.0 is the opt-out back
to the old equal-weight fusion (any other finite value, e.g. =3.0, is honored verbatim).
Evidence (grep -n "dense-weight flip" CHANGELOG.md): NL ndcg@10 0.3047->0.4466, zero
per-category regression. Version note (verified, do not propagate the mislabel): the
v1.93.4-era CHANGELOG #712 entry calls this "the v1.93.2 dense-weight flip", but
git tag --contains c1d4ba4 puts the flip commit's FIRST release at v1.79.0 (its own
CHANGELOG entry sits under ## v1.79.0 (2026-07-16)) — the v1.93.2 label in that later entry is
a mislabel. The old item-(2) text above is kept as dated history: it was accurate for the
#628/#630 flip-prep era and is closed by the flip.
SUPERSEDED (same 2026-08-12 pass): STATUS UPDATE 2 item (1)'s MaxSim / TG_LATE_RERANK
"re-run the tg find gate" instruction is RETIRED — do NOT re-run. The module docstring is the
authority: src/tensor_grep/core/retrieval_late.py:4-16 (note: core/, not cli/) records
"RETIRED 2026-08-05 (task F10)". The post-role-aware-encoder-fix re-measurement HAPPENED and was
decisively negative: ndcg@10 0.068 vs plain RRF 0.305 (docs/PAPER.md:469), root cause model
capacity (the 17M-param int8 LateOn-Code-edge model's raw MaxSim ranking is statistically
indistinguishable from random on in-repo code) — NOT the encoder wiring, so "re-flipping the same
encoder will not change the verdict." The module is kept in place, not deleted (the
core/reranker.py wiring is too load-bearing to touch for a demand-gated feature), reachable only
behind the undocumented TG_LATE_RERANK=1 env var with no tg-command install path. Reopen only
on BOTH a real tg-command install path AND a different encoder clearing the design doc's T8
golden-set thresholds — never off a partial win on either alone.
tg install-dense
One-shot dense-leg setup
Installs the semantic extra (model2vec+numpy, torch-free) via the same uv tool → uv pip → pip cascade tg upgrade uses, then fetches the checksum-pinned potion-code-16M model; fails closed (non-zero exit, no partial model directory) on any pip/network/checksum failure — never a silent half-installed state. Every dense-absent hint across the CLI (tg search --semantic, tg find's rank_fallback_reason) now leads with tg install-dense (v1.93.0/#705) instead of a bare "pip install the extra" instruction.
src/tensor_grep/core/retrieval_scoring.py
Metrics
recall_at_k, precision_at_k, mean_reciprocal_rank_at_k, ndcg_at_k, f1_score, RetrievalMetrics. These are the promotion yardsticks — use them, don't invent new ones.
Produce evidence here; let tensor-grep-change-control gate the flip.
main.py
AGENTS.md
file:line
main.py
_can_passthrough_rg
5249
5462
rerank_by_bm25
8067
8411
--semantic
7141
7403
AGENTS.md
:525
:1701
:565
:1741
:496
:1672
:539-541
:1715
:561-562
:1738
grep <symbol>
was -> now
3883
5249
5462
config.py:182/183/188
bootstrap.py:68-70
pyproject.toml:627
semantic_index.py:34
retrieval_bm25.py:18-19
retrieval_chunker.py:37
README.md:38,212
main.py
AGENTS.md
repo_map.py
grep -m1 release_docs_current_tag AGENTS.md
grep -m1 '"version"' npm/package.json
Dense leg + RRF now shipped:ls src/tensor_grep/core/retrieval_dense.py src/tensor_grep/core/retrieval_fusion.py;
grep -n "\-\-semantic" src/tensor_grep/cli/main.py src/tensor_grep/cli/bootstrap.py;
grep -n "semantic = " pyproject.toml (the optional extra, now at pyproject.toml:627).
BM25 leg + defaults:Read src/tensor_grep/core/retrieval_bm25.py (DEFAULT_K1=1.5,
DEFAULT_B=0.75, retrieval_bm25.py:18-19), retrieval_chunker.py (chunk_size=30, overlap=5,
MAX_CHUNKS=100_000 at retrieval_chunker.py:37) — all re-verified 2026-07-24 against v1.96.0.
--rank wiring + default-OFF:grep -n "rank_bm25" src/tensor_grep/core/config.py
(default False, config.py:183), grep -n "rerank_by_bm25\|not config.rank_bm25\|rank_bm25=rank" src/tensor_grep/cli/main.py,
grep -n "\-\-rank\|\-\-bm25" src/tensor_grep/cli/bootstrap.py (TG-only flag front door, now bootstrap.py:68-69).
The gate:grep -n "V2_GATE_RECALL\|must beat" benchmarks/eval_bm25_quality.py (0.60), and run
uv run --no-sync python benchmarks/eval_bm25_quality.py --top-k 3 (expect recall 1.000 — the floor).
Governance: roadmap item #1 AGENTS.md §"Roadmap Sequencing" (grep -n "Roadmap Sequencing" AGENTS.md;
heading was :525 at v1.96.0, now :1701 at v1.101.27; Semble reference grep -n "MinishLab .Semble." AGENTS.md
was :565, now :1741 — re-verified 2026-08-01, drifted ~1176 lines in one week, cite the grep not the number);
the "only when it demonstrably beats the shipped baseline on both retrieval quality and editor-plane" rule
grep -n "editor-plane" README.md (:212, re-verified 2026-08-01, unchanged); backend contract
AGENTS.md §"Backend Fail-Closed Contract" (grep -n "Backend Fail-Closed Contract" AGENTS.md;
heading was :496, now :1672).
Benchmarks:ls benchmarks/eval_bm25_quality.py benchmarks/eval_late_rerank_quality.py benchmarks/run_editor_plane_benchmarks.py — run_repo_retrieval_benchmarks.py still exists but is a static-fixture replay, not the live chunker-sensitive gate (re-confirmed 2026-07-24: zero chunk_file hits in that file; see STATUS UPDATE 3 / Phase 4 above).
Persisted-index building blocks (unwired):Read src/tensor_grep/core/semantic_index.py
(env TG_SEMANTIC_INDEX_DIR, .tg_semantic_index/, INDEX_VERSION=2 as of v1.96.0 — bumped
from 1 when the structural chunker landed, semantic_index.py:34; still no tg index command).