feat(query): position-weighted seeding + coherence gate for LIP rerank#209
Merged
feat(query): position-weighted seeding + coherence gate for LIP rerank#209
Conversation
The Fast-tier LIP rerank built its query centroid from the top-5 lexical
results, uniformly averaged — two problems:
1. Lexical-bias feedback loop: if the top-5 lexical hits are wrong, the
centroid inherits the bias verbatim and amplifies it downstream.
2. No signal-strength check: orthogonal / scattered seeds produced a
near-zero centroid that still got the same 0.4 semantic weight as a
strong, coherent signal.
Changes:
- Position-weighted seeding (1/(rank+1)) with each seed L2-normalised
before averaging — top-1 dominates softly rather than uniformly.
- Centroid coherence gate: since each seed is unit-length, the resulting
centroid norm lies in [0, 1] and is a direct measure of seed agreement.
Below MinCoherence (0.35) we skip the semantic pass and return lexical
order — better to be lexical-only than to amplify noise.
- Extract RerankConfig {LexicalWeight, SemanticWeight, SeedCount,
MinCoherence} with DefaultRerankConfig(). Defaults match prior
behaviour; per-repo tuning is now a config change, not a code change.
- Inject embedBatchFn so the rerank logic is unit-testable without a
running LIP daemon.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
NFR Tests ✅ 39 unchangedComparing PR against main branch (dynamic baseline). Regressions: 0 ✅ Thresholds: WARN ≥ +5% • FAIL ≥ +10% All scenarios
* = new scenario, compared against static baseline |
🟢 Change Impact Analysis
Blast Radius: 0 modules, 0 files, 0 unique callers 📝 Changed Symbols (1)
Recommendations
Generated by CKB |
CKB Analysis
Risk factors: Touches 1 hotspot(s) 👥 Suggested: @lisa.welsch1985@gmail.com (100%), @lisa@tastehub.io (50%)
🎯 Change Impact Analysis · 🟢 LOW · 1 changed → 0 affected
Symbols changed in this PR:
Recommendations:
💣 Blast radius · 0 symbols · 1 tests · 0 consumersTests that may break:
🔥 Hotspots · 1 volatile files
📊 Complexity · 1 violations
💡 Quick wins · 10 suggestions
📚 Stale docs · 197 broken references
Generated by CKB · Run details |
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## main #209 +/- ##
=======================================
+ Coverage 43.0% 43.1% +0.1%
=======================================
Files 526 526
Lines 81101 81133 +32
=======================================
+ Hits 34914 35018 +104
+ Misses 43719 43647 -72
Partials 2468 2468
Flags with carried forward coverage won't be shown. Click here to find out more. 📢 Thoughts on this report? Let us know! 🚀 New features to boost your workflow:
|
|
CKB review failed to generate output. |
SimplyLiz
added a commit
that referenced
this pull request
Apr 15, 2026
CKB used to re-probe IndexStatus on a 60 s TTL every time the rerank path checked whether the LIP index was mixed-models. The LIP daemon has pushed IndexChanged frames to all active sessions since v1.5.0, so the polling was pure debt. New internal/lip/subscribe.go opens a long-lived connection, pings IndexStatus every 3 s to flush the daemon's broadcast channel (the session loop drains queued pushes only after writing a response), reads every frame in a loop, and routes index_changed and index_status by type tag. Reconnects with exponential backoff to 30 s on daemon drop. Engine owns one subscriber, started in NewEngine and cancelled in Close. The cached availability/mixed flags are now written by the subscriber, not the query path — lipSemanticAvailable is lock-free RLock+read, no RPC. Worst-case staleness for the rerank gate drops from 60 s to ~3 s. Tests rewritten: the fake daemon now serves multiple requests per connection and tests wait for the first health frame before asserting, plus a new TestLipSubscriber_ReusesSingleConnection verifies the hot path issues zero requests. Also: CHANGELOG entries for #208 and #209 which landed on develop without one. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
6 tasks
SimplyLiz
added a commit
that referenced
this pull request
Apr 15, 2026
* feat(lip): push-driven health via long-lived subscribe CKB used to re-probe IndexStatus on a 60 s TTL every time the rerank path checked whether the LIP index was mixed-models. The LIP daemon has pushed IndexChanged frames to all active sessions since v1.5.0, so the polling was pure debt. New internal/lip/subscribe.go opens a long-lived connection, pings IndexStatus every 3 s to flush the daemon's broadcast channel (the session loop drains queued pushes only after writing a response), reads every frame in a loop, and routes index_changed and index_status by type tag. Reconnects with exponential backoff to 30 s on daemon drop. Engine owns one subscriber, started in NewEngine and cancelled in Close. The cached availability/mixed flags are now written by the subscriber, not the query path — lipSemanticAvailable is lock-free RLock+read, no RPC. Worst-case staleness for the rerank gate drops from 60 s to ~3 s. Tests rewritten: the fake daemon now serves multiple requests per connection and tests wait for the first health frame before asserting, plus a new TestLipSubscriber_ReusesSingleConnection verifies the hot path issues zero requests. Also: CHANGELOG entries for #208 and #209 which landed on develop without one. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * chore: drop stray cmd/ckb-bench/version_test.go Had a package-level init() that printed cartographer version on import under the cartographer build tag — not a test, and leaked output into unrelated test runs when the tag was enabled. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Two long-standing issues in the Fast-tier LIP re-ranker (
internal/query/lip_ranker.go):This PR:
1/(rank+1), each seed L2-normalised before averaging. Top-1 still dominates, but softly — lower-ranked seeds pull the centroid proportionally less.||centroid||lies in[0, 1]and directly measures seed agreement. BelowMinCoherence(default 0.35) the rerank skips the semantic pass and returns lexical order — fall back instead of amplify noise.RerankConfigstruct (LexicalWeight,SemanticWeight,SeedCount,MinCoherence) +DefaultRerankConfig(). Defaults preserve prior 0.6/0.4/5 behaviour; per-repo tuning no longer requires a recompile.embedBatchFnso the rerank logic is unit-testable without a running LIP daemon.No call-site changes —
RerankWithLIP(ctx, results, repoRoot, query)signature unchanged.Test plan
lip_ranker_test.go:buildSeedCentroidcoherence in {aligned, opposed, single-seed} casesgo test ./internal/query/...— full package passes.go build ./...clean.gofmtclean.Follow-ups (not in this PR)
RerankConfigvalues — the current defaults are a starting point, not a proven optimum.🤖 Generated with Claude Code