v2.5.2b2 — beta: recall ranking correction (bug-155) and a storage policy the maintenance layer respects (bug-182)
Pre-releasePre-release (second beta of the 2.5.2 stabilization ladder; the latest final release remains v2.5.1, which pip installs by default — pre-releases require explicit opt-in).
b1 changed what callers see. This rung changes what recall returns and what the maintenance layer does about a configuration it had been misreading. No contract break — the tool responses and the DB schema are the ones b1 published, so upgrade and rollback across this cut need no migration and no caller change.
Ranking correction
-
Vector-less rows no longer out-score real vector hits (
bug-155, HIGH). UnderCONFIDENCE_ENABLED, rows admitted through the FTS or keyword channel reached_apply_recall_scoringwith_cosine=None— even when their embedding was sitting in the DB — and_compute_confidence'sNonebranch (sqrt(time_decay) * …) scored at least as high as its cosine branch (sqrt(norm_cos * time_decay) * …). The keyword channel, documented as a way to down-rank lexical contaminants, was structurally promoting them instead. The fix backfills the cosine for those rows before scoring.Measured on LMEB: +14.27 NDCG@10 on
rsf, 16 of 16 tasks improved,cascadeundisturbed (it does not route through this seam). This is a default-behaviour change: the same query against the same database can come back in a different order — that is the point of the fix, and the reason it ships as a beta rung rather than inside a patch.
Storage policy, respected
CPERSONA_STORE_BLOB=falseis a policy, not a broken pipeline (bug-182, HIGH). The maintenance layer did not know that. On a correctly configured remote deployment — wheredo_storedeliberately writes no local embedding —check_null_embeddingread every NULL row as a dead embedding pipeline:severity=criticalpermanently (>50% NULL by construction), and everycheck_health(fix=true)re-embedded up to 500 memories over HTTP and wrote back the very BLOBs the operator had configured away, which the nextstoremade NULL again. The rule now has one home,config.local_blobs_stored, and the checks read it. Episodes are deliberately out of scope: they have no storage gate and always write a blob.- The empty safety net now says so (
bug-180, LOW). Under remote mode with that same setting, the local cosine scan — documented as the fallback for a/searchoutage — matches nothing, becauseembedding IS NOT NULLis false for every memory by construction. A/searchfault with a working/embedtherefore dropped recall to FTS/keyword silently; the degraded advisory watched only the embed boundary.
Supply-chain surface
- The wheel stops shipping dead vendored code (
bug-181, LOW). Every wheel carried ~2,479 LOC of vendored modules with no import path from any cpersona module (llm_provider.py,mcp_stream_interceptor.py,mgp_utils.py,search.py,semantic_cache.py) — unreachable by construction, yet on every user's disk, enlarging the audited and supply-chain surface (llm_provideralone carries outbound third-party HTTP). They are excluded at the wheel boundary only: the in-tree vendored snapshot stays byte-faithful to upstream, so the sync gate still compares like for like. An AST import-graph gate and awheel-smokeCI job guard the two ends.
Verification
- Full suite: 683 passed, 5 skipped; ruff clean;
verify-issues.sh182 entries, 0 errors, 0 stale; waiver registry clean; CI green on the bump commit (lint,test (3.11),test (3.13),wheel-smoke,verify-issues,mutation-proof). - The cosine backfill carries hand-applied mutation evidence, re-proven after the fix was rebased onto master.
- PyPI artifacts (wheel + sdist) published via OIDC Trusted Publishing with provenance.
Known gap (unchanged from b1)
The automated mutation-diff lane is advisory and still skips everything when the diff exceeds its cap rather than sampling a subset. Of the two changes in this cut, bug-155 (112 in-scope mutants, cap 60) was not executed for that reason; the bug-182/bug-180/bug-181 change (55 in-scope) ran and returned 43 killed / 12 survived. A capped run and a passing run currently render the same colour in the checks list. Tracked — the fix makes large diffs sample instead of opt out.