Skip to content

v2.5.2b2 — beta: recall ranking correction (bug-155) and a storage policy the maintenance layer respects (bug-182)

Pre-release
Pre-release

Choose a tag to compare

@Cloto-dev Cloto-dev released this 25 Jul 10:10

Pre-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). Under CONFIDENCE_ENABLED, rows admitted through the FTS or keyword channel reached _apply_recall_scoring with _cosine=None — even when their embedding was sitting in the DB — and _compute_confidence's None branch (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, cascade undisturbed (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=false is a policy, not a broken pipeline (bug-182, HIGH). The maintenance layer did not know that. On a correctly configured remote deployment — where do_store deliberately writes no local embedding — check_null_embedding read every NULL row as a dead embedding pipeline: severity=critical permanently (>50% NULL by construction), and every check_health(fix=true) re-embedded up to 500 memories over HTTP and wrote back the very BLOBs the operator had configured away, which the next store made 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 /search outage — matches nothing, because embedding IS NOT NULL is false for every memory by construction. A /search fault with a working /embed therefore 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_provider alone 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 a wheel-smoke CI job guard the two ends.

Verification

  • Full suite: 683 passed, 5 skipped; ruff clean; verify-issues.sh 182 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.