Skip to content

feat(server): kb.explain_ranking — expose why a result ranked where it did (#432)#457

Open
moorsecopers99 wants to merge 3 commits into
vouchdev:mainfrom
moorsecopers99:feat/explain-ranking
Open

feat(server): kb.explain_ranking — expose why a result ranked where it did (#432)#457
moorsecopers99 wants to merge 3 commits into
vouchdev:mainfrom
moorsecopers99:feat/explain-ranking

Conversation

@moorsecopers99

@moorsecopers99 moorsecopers99 commented Jul 10, 2026

Copy link
Copy Markdown

what changed

adds kb.explain_ranking, a read-only introspection method over the retrieval pipeline. per candidate it returns the lexical (fts5) rank, the semantic (embedding) rank, the rrf contribution, the rerank delta (when #5's reranker extras are installed), the salience factor, and the gate outcome — kept, budget-dropped, uncited, or status-filtered. it re-runs _retrieve's own primitives (search_semantic, search, rrf_fuse, filter_hits) in an instrumented mode rather than duplicating the scoring math, and classifies each candidate's gate by mirroring build_context_pack's status → budget → uncited pipeline. exposed on all four surfaces: vouch explain-ranking (cli, --format text|json), kb_explain_ranking (mcp), kb.explain_ranking (jsonl), and registered in capabilities.METHODS.

why

_retrieve hands back each hit as (kind, id, summary, score, backend) — one opaque score and a backend label. a reviewer tuning retrieval (fusion weights, the reranker in #5, the recency/frequency signals in #317) has no way to see why an artifact surfaced or got dropped: how much came from lexical vs semantic rank, what the rrf contribution was, whether a gate cut it. this makes the ranker inspectable exactly where it was a black box. the ui panel stays in the vouch-ui console per the issue — this is the backend that feeds it. closes #432.

proof

captured from the shipped cli against a fresh kb — two approved claims, one archived. the fusion path is reported active, lexical rank and rrf contribution are populated, and the archived claim surfaces as status-filtered; tightening --max-chars flips the kept claim to budget-dropped:

$ vouch explain-ranking "jwt tokens" --limit 5
query: jwt tokens  backend=hybrid  stages=fusion
[kept] claim/jwt-tokens-expire-after-one-hour  lex=1 sem=- rrf=0.0164 rerankΔ=- salience=- score=0.0164
[status-filtered] claim/jwt-refresh-tokens-live-for-thirty-days  lex=2 sem=- rrf=0.0161 rerankΔ=- salience=- score=0.0161

$ vouch explain-ranking "jwt tokens" --max-chars 20
query: jwt tokens  backend=hybrid  stages=fusion
[budget-dropped] claim/jwt-tokens-expire-after-one-hour  lex=1 sem=- rrf=0.0164 rerankΔ=- salience=- score=0.0164
[status-filtered] claim/jwt-refresh-tokens-live-for-thirty-days  lex=2 sem=- rrf=0.0161 rerankΔ=- salience=- score=0.0161

the json surface returns the same breakdown per candidate and carries the usual _meta.vouch_trust stamp.

what might break

  • new kb.* method. kb.explain_ranking joins the method surface on all three transports plus the cli. adapters that enumerate methods will see it; test_capabilities (methods ↔ jsonl handlers ↔ mcp tools parity) is updated and green.
  • pure read-time introspection — no writes, no proposals, no kb.approve, no lifecycle change, no new audit events. it exposes only what kb.context already computes internally and is viewer-scoped identically, so it can't reveal an artifact a caller couldn't already retrieve (covered by a private-claim parity test). storage.py stays pure i/o; the instrumentation lives in the retrieval layer.
  • no files move; no on-disk field changes shape; no existing method changes behaviour. no migration.

vep

this adds a new kb.* method, which per the template and proposals/README.md is a surface change that wants an accepted vep first. flagging it honestly: the issue (#432) is assigned and in milestone 2 and already specifies the surface, and this is a read-only, additive, viewer-scoped introspection method with no object-model or on-disk change. happy to write up a short vep if you'd like this gated on one before review.

tests

  • make check passes locally (lint + mypy + pytest) — pytest 1092 passed / 28 skipped, mypy clean across 89 files, ruff clean. run with the [dev,web] extras to match ci.
  • new / changed behaviour has a test — tests/test_explain_ranking.py, 12 cases: fused-only query signals, fused-rank ordering, status-filtered / budget-dropped / uncited gates, require-citations happy path, viewer-scoping parity with kb.context, read-only invariance (no proposals, no audit growth), empty-query, and the four-site registration plus a jsonl round-trip.
  • CHANGELOG.md updated under ## [Unreleased].

Summary by CodeRabbit

  • New Features
    • Added a read-only ranking introspection capability (kb.explain_ranking / vouch explain-ranking) that explains per-candidate ranking components and gate outcomes.
    • Works across retrieval, optional reranking, optional salience weighting, citation enforcement, viewer scoping, and max_chars budgeting, with JSON or human-readable output.
    • Exposed via the JSONL interface and an MCP tool (kb_explain_ranking).
  • Documentation
    • Updated the Unreleased changelog entry for the new capability.
  • Tests
    • Added comprehensive automated coverage for ranking, gating, scoping, formatting, and error handling.

adds a read-only method that re-runs the kb.context retrieval pipeline
in an instrumented mode and reports, per candidate, the lexical (fts5)
rank, the semantic (embedding) rank, the rrf contribution, the rerank
delta (when the reranker extras are installed), the salience factor,
and the gate outcome (kept / budget-dropped / uncited / status-filtered).

it reuses _retrieve's own primitives rather than duplicating the scoring
math, and is viewer-scoped identically to kb.context so it can never
expose an artifact the caller couldn't already retrieve. recency and
frequency factors are reported null until vouchdev#317 lands; the rerank delta
is populated only when vouchdev#5's reranker extras are present.

registered on all four surfaces — mcp (kb_explain_ranking), jsonl
(kb.explain_ranking), capabilities.METHODS, and cli (vouch
explain-ranking with --format text|json) — plus tests in
tests/test_explain_ranking.py covering a fused-only query, each gate
drop, viewer-scoping parity, and the four-site registration.

closes vouchdev#432
@coderabbitai

coderabbitai Bot commented Jul 10, 2026

Copy link
Copy Markdown

Review Change Stack

Warning

Review limit reached

@moorsecopers99, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 37 minutes

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 817bab3a-a912-4704-88e7-aae1bed197d3

📥 Commits

Reviewing files that changed from the base of the PR and between 75977d6 and cd80cd6.

📒 Files selected for processing (4)
  • src/vouch/cli.py
  • src/vouch/context.py
  • src/vouch/jsonl_server.py
  • tests/test_explain_ranking.py
📝 Walkthrough

Walkthrough

Adds read-only kb.explain_ranking introspection across the retrieval pipeline, CLI, JSONL, MCP, and capability registry. It reports ranking components, optional rerank and salience data, viewer-scoped candidates, and gate outcomes.

Changes

Ranking introspection

Layer / File(s) Summary
Instrumented retrieval and gate classification
src/vouch/context.py, tests/test_explain_ranking.py
Retrieval explanations record lexical, semantic, fused, rerank, and salience fields, then classify status, budget, and citation gates while preserving viewer scoping and read-only behavior.
CLI and service interface wiring
src/vouch/cli.py, src/vouch/jsonl_server.py, src/vouch/server.py, src/vouch/capabilities.py, tests/test_explain_ranking.py, CHANGELOG.md
The feature is exposed through CLI, JSONL, MCP, and capabilities registration, with end-to-end wiring tests and changelog documentation.

Estimated code review effort: 4 (Complex) | ~45 minutes

Sequence Diagram(s)

sequenceDiagram
  participant Caller
  participant CLI_JSONL_MCP
  participant explain_ranking
  participant KBStore
  participant _classify_gates
  Caller->>CLI_JSONL_MCP: request ranking explanation
  CLI_JSONL_MCP->>explain_ranking: forward query, filters, and viewer scope
  explain_ranking->>KBStore: retrieve scoped candidates
  explain_ranking->>_classify_gates: apply status, budget, and citation gates
  _classify_gates-->>explain_ranking: candidate outcomes
  explain_ranking-->>CLI_JSONL_MCP: structured explanation
  CLI_JSONL_MCP-->>Caller: JSON or formatted results
Loading

Possibly related PRs

  • vouchdev/vouch#43: Both changes expose additional ranking and backend metadata from the context retrieval pipeline.
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 52.94% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title is concise and clearly describes the new kb.explain_ranking feature.
Linked Issues check ✅ Passed The PR adds read-only kb.explain_ranking, exposes it across CLI/MCP/JSONL/capabilities, preserves viewer scoping, and tests fused and gate-dropped cases.
Out of Scope Changes check ✅ Passed The changes stay focused on ranking introspection and its registrations, tests, and changelog entry.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@github-actions github-actions Bot added docs documentation, specs, examples, and repo guidance cli command line interface mcp mcp, jsonl, and http surfaces retrieval context, search, synthesis, and evaluation tests tests and fixtures size: L 500-999 changed non-doc lines labels Jul 10, 2026

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (1)
src/vouch/context.py (1)

176-211: 📐 Maintainability & Code Quality | 🔵 Trivial | 🏗️ Heavy lift

Backend dispatch logic is duplicated from _retrieve (lines 92–126).

The if/elif/else control flow — backend selection, hybrid fallback to substring, per-backend filter_hits calls — mirrors _retrieve but is maintained separately. Adding a new backend or changing fallback behavior requires updating both functions in sync, risking silent divergence.

Consider extracting a shared dispatch helper that returns the raw hits plus metadata (backend name, fusion flag, sem/lex rank maps), so both _retrieve and explain_ranking consume the same primitive.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/vouch/context.py` around lines 176 - 211, Extract the duplicated backend
dispatch from `_retrieve` and `explain_ranking` into a shared helper that
performs backend selection, hybrid substring fallback, filtering inputs, and
rank-map/fusion metadata assembly. Have the helper return raw hits plus
`used_backend`, `fusion`, `sem_rank`, and `lex_rank`, then update both callers
to consume it so backend behavior and fallback logic remain synchronized.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@tests/test_explain_ranking.py`:
- Around line 223-237: Expand test_jsonl_handler_round_trip to assert the
response id equals "r1" and verify the complete successful envelope, including
ok and result. Add a failure-case test for kb.explain_ranking using invalid or
missing parameters, asserting the response preserves the request id, has ok set
to false, and includes an error field.

---

Nitpick comments:
In `@src/vouch/context.py`:
- Around line 176-211: Extract the duplicated backend dispatch from `_retrieve`
and `explain_ranking` into a shared helper that performs backend selection,
hybrid substring fallback, filtering inputs, and rank-map/fusion metadata
assembly. Have the helper return raw hits plus `used_backend`, `fusion`,
`sem_rank`, and `lex_rank`, then update both callers to consume it so backend
behavior and fallback logic remain synchronized.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: e7b54a35-90ac-4cae-a953-9b28df815d16

📥 Commits

Reviewing files that changed from the base of the PR and between 5c98a3a and ec52cc4.

📒 Files selected for processing (7)
  • CHANGELOG.md
  • src/vouch/capabilities.py
  • src/vouch/cli.py
  • src/vouch/context.py
  • src/vouch/jsonl_server.py
  • src/vouch/server.py
  • tests/test_explain_ranking.py

Comment thread tests/test_explain_ranking.py
add the request-id assertion on the success round-trip and a
failure-case test for a missing query param, asserting the
{id, ok: false, error} envelope with code missing_param — per the
coding guideline for new kb.* methods.
add docstrings to the nested retrieval helpers (_ranks, _clipped_len,
_fmt), the jsonl handler, and the test helpers/cases that lacked them,
clearing the 80% docstring-coverage threshold on the diff.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

cli command line interface docs documentation, specs, examples, and repo guidance mcp mcp, jsonl, and http surfaces retrieval context, search, synthesis, and evaluation size: L 500-999 changed non-doc lines tests tests and fixtures

Projects

None yet

Development

Successfully merging this pull request may close these issues.

feat(server): kb.explain_ranking — expose why a result ranked where it did

2 participants