Skip to content

feat(retrieval): Calibrate strict-recall threshold, add filter_score config, pass through min_score via MCP/hook, and add full evaluation report - #63

Open
ch-qiaoyongheng wants to merge 12 commits into
afx-team:mainfrom
ch-qiaoyongheng:feat/issue-31-recall-floor-configurable
Open

feat(retrieval): Calibrate strict-recall threshold, add filter_score config, pass through min_score via MCP/hook, and add full evaluation report#63
ch-qiaoyongheng wants to merge 12 commits into
afx-team:mainfrom
ch-qiaoyongheng:feat/issue-31-recall-floor-configurable

Conversation

@ch-qiaoyongheng

@ch-qiaoyongheng ch-qiaoyongheng commented Jul 31, 2026

Copy link
Copy Markdown

Summary

Promote the hardcoded _RERANK_FLOOR_RATIO constant to a configurable Settings field, pass through min_score via MCP server and Claude Code recall hook, and calibrate the default threshold through a full LoCoMo evaluation (1,978 queries) — confirming that sigmoid scores are unsuitable for hard filtering and recommending filter_score = 0.6 with composite score filtering.

Closes #31

Background

Audit C5 / Recall F3 identified that the post-rerank candidate pool carries cross-encoder sigmoid scores while the tail retains composite disp_score — two different distributions.
Applying a single recall_min_score=0.8 threshold causes bge-reranker to silently clear strict recall on correct but non-literal short-conversation hits (39% of queries return empty results).

Issue #31 divided the work into three parts:

  1. Promote the rerank_floor_ratio hardcoded constant to a configurable field
  2. Pass through min_score per-call overrides via two production surfaces (MCP server / Claude Code recall hook)
  3. Validate and recalibrate defaults based on annotated datasets

After a full LoCoMo evaluation (1,978 queries) and discussion with the team, the new approach was confirmed:

  • No longer retain rerank_floor_ratio as the default production mechanism (existing parameters marked with DEPRECATED comments)
  • Introduce a semantically clear filter_score for composite-score-based filtering
  • Core logic: reranker only handles ranking, composite score handles filtering

Task 1: Add filter_score configuration to replace the old dual-scale filtering

Problem with the original approach:
Using rerank_floor_ratio to map composite scores to the sigmoid scale for gating, but evaluation proved sigmoid scores are unsuitable for hard filtering.

New approach:

  • Add filter_score configuration field (default 0.6, range [0,1])
  • Directly use composite score for filtering, avoiding sigmoid scale mismatch
  • Mark rerank_floor_ratio and recall_min_score as DEPRECATED
  • Retain recall_hook_min_score as a deployment-level override

Modified files:

  • src/hebb/config/settings.py: Add filter_score field, mark old fields as DEPRECATED
  • src/hebb/models/memory.py: Add filter_score field to MemoryQuery
  • src/hebb/server/routers/search.py: Update routing logic to prioritize filter_score
  • src/hebb/retrieval/searcher.py: Update filtering logic to prioritize composite score filtering

Task 2: Pass through min_score via MCP server and recall hook

Problem with the original approach:
MCP server and recall hook hardcode strict_recall: True, unable to pass per-call overrides.

New approach:

  • MCP server adds filter_score parameter, taking priority over min_score
  • Recall hook reads recall_hook_min_score configuration and uses it as filter_score
  • Retain min_score parameter as a rollback option

Modified files:

  • src/hebb/mcp/server.py: Add filter_score parameter, support min_score pass-through
  • src/hebb/integrations/claude_code/recall.py: Add filter_score configuration reading and passing

Task 3: Threshold calibration — validate defaults based on evaluation data

Problem with the original approach:
recall_min_score=0.8 and _RERANK_FLOOR_RATIO=0.625 were heuristically derived and never validated on annotated datasets.

Evaluation probe:

  • Added eval/threshold_probe.py: Offline probe for full min_score × rerank_floor_ratio parameter sweep, reporting R@k, empty recall ratio, and score distribution statistics

Full evaluation (LoCoMo, 1,978 queries)

Mode Description Baseline R@10 Current default R@10 Current default empty frac
A Composite only (no reranker) 91.5% 63.6% 27.5%
B Reranker + sigmoid floor (current prod) 94.6% 53.5% 39.0%
C Reranker + composite floor 94.6% 63.6% 27.5%

Key finding

Sigmoid scores are unsuitable for hard filtering — the root cause is not "which threshold to choose" but "wrong filtering dimension":

Score system p50 Note
Composite 69.4% Healthy distribution
Sigmoid (relevant hits) 16.6% Original "~0.5" assumption off by 3x
Sigmoid (all) 3.9% Most scores cluster near 0

Full 160-combination sigmoid sweep: no pair achieves R@10 ≥ 90% with empty result rate < 5%.

Recommended approach:

Reranker handles ranking; composite score handles filtering.

Config R@10 Empty frac Avg results
Current default (0.8, sigmoid) 53.5% 39.0% 1.4
Recommended (0.6, composite) 91.5% 1.0% 7.3
Lenient (0.5, composite) 93.0% 0.0% 8.7

Recommended default: Use composite filter threshold filter_score = 0.6.


Task 4: Frontend configuration UI update

Modified files:

  • src/hebb/static/js/components/activate.js: Remove recall_min_score and rerank_floor_ratio, add filter_score
  • src/hebb/static/js/components/config-section.js: Update validation logic, remove old field handling
  • src/hebb/static/js/i18n.js: Add Chinese and English translations for filter_score

Checklist

Item Status Note
ruff check src/ Passed No errors
mypy src/hebb/ Passed 6 pre-existing errors, unrelated to our changes
CHANGELOG.md updated Updated New changes added
Documentation updated Not needed Config auto-exposed via web console
Tests added or updated Done 749 tests passing (620 unit + 141 integration)
No keys, credentials, or local config files committed Confirmed No sensitive info committed

Review Notes

Evaluation artifacts:

  • Full evaluation results: eval/reports/threshold_calibration/full-{A,B,C}_*.json
  • V2 review report: eval/reports/threshold_calibration/threshold_calibration_review_v2.md

Audit items:

  • C5 / Recall F3 — Strict-recall threshold calibration

Key decisions:
This change only adjusts the filtering threshold and filtering dimension (sigmoid → composite), without altering the relative ranking order of results — a monotonic threshold-only change that, per the issue's release rules, does not require A/B testing.
rerank_floor_ratio is retained as a configurable field as an emergency rollback switch — to restore sigmoid filtering, just modify hebb.json without redeployment.

CodeRabbit Summary

New features:

  • Add configurable minimum score threshold for memory search, including per-request overrides
  • Add rerank-floor and recall-hook settings with configurable defaults
  • Add validation and localization guidance in the configuration UI
  • Add offline threshold calibration tool and full evaluation report

Bug fixes:

  • Preserve composite scores during reranking for more consistent filtering
  • Improve strict-recall behavior and empty result reporting

Tests:

  • Add test coverage for score overrides, recall-hook settings, and rerank-floor boundaries
  • 749 tests passing with good coverage

Summary by CodeRabbit

  • New Features
    • Added composite-score filtering for stricter, more predictable search recall.
    • Added per-request and recall-hook score overrides.
    • Added threshold-calibration tools and reports covering recall, empty results, and result counts.
    • Preserved pre-reranking scores for greater result transparency.
  • Improvements
    • Lowered the default recall threshold from 0.8 to 0.6.
    • Added frontend validation for score values between 0 and 1.
  • Deprecations
    • Marked legacy minimum-score and reranking-floor options for fallback use.

@gemini-code-assist

Copy link
Copy Markdown

Caution

The consumer version of Gemini Code Assist on GitHub has been sunset. All code review activity has officially ceased.

@coderabbitai

coderabbitai Bot commented Jul 31, 2026

Copy link
Copy Markdown

Review Change Stack

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

The PR adds LoCoMo threshold calibration, composite-score strict-recall filtering, configurable request thresholds, recall-hook overrides, frontend validation, calibration reports, and regression tests.

Changes

Strict-recall calibration

Layer / File(s) Summary
Threshold probe and retrieval metrics
eval/threshold_probe.py, eval/retrieval_lab.py
Adds threshold sweeps, Recall@k metrics, empty-result rates, score distributions, CLI options, and JSON output.
Calibration reports and recommendations
eval/reports/threshold_calibration/*, CHANGELOG.md
Adds composite, sigmoid, and reranker calibration results with threshold recommendations, default comparisons, and rollout guidance.

Production retrieval configuration

Layer / File(s) Summary
Composite-score retrieval contract
src/hebb/config/settings.py, src/hebb/models/memory.py, src/hebb/retrieval/searcher.py, src/hebb/server/routers/search.py
Adds filter_score, preserves pre_rerank_score, applies composite-score filtering, and retains rerank_floor_ratio as a deprecated fallback.
Request and hook threshold overrides
src/hebb/mcp/server.py, src/hebb/integrations/claude_code/recall.py
Adds optional min_score and filter_score parameters. filter_score takes precedence, and strict recall remains enabled.
Frontend validation and regression coverage
src/hebb/static/js/components/*, src/hebb/static/js/i18n.js, tests/integration/server/test_api.py, tests/unit/integrations/test_claude_code_hooks.py, tests/unit/test_audit_retrieval.py
Updates recall settings, validates thresholds from 0 to 1, localizes validation text, and tests threshold precedence and fallback behavior.

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

Sequence Diagram(s)

sequenceDiagram
  participant Client
  participant SearchRouter
  participant Searcher
  participant Reranker
  Client->>SearchRouter: send filter_score or min_score
  SearchRouter->>SearchRouter: apply configured defaults
  SearchRouter->>Searcher: execute strict-recall search
  Searcher->>Reranker: rerank candidate results
  Searcher->>Searcher: filter by pre_rerank_score
  Searcher-->>Client: return filtered results
Loading

Possibly related PRs

  • afx-team/hebb-mind#47: Modifies Claude Code recall-hook integrations and shared score-threshold forwarding.
  • afx-team/hebb-mind#58: Modifies related strict-recall thresholds, rerank-floor handling, retrieval filtering, MCP, Claude Code hooks, and calibration evaluation.

Suggested reviewers: afx-team, ch-liuzhide

🚥 Pre-merge checks | ✅ 3 | ❌ 2

❌ Failed checks (2 warnings)

Check name Status Explanation Resolution
Linked Issues check ⚠️ Warning For [#31], calibration and override objectives are met, but MCP and Claude Code cannot reach the documented legacy dual-scale rollback path. Preserve a reachable legacy min_score/rerank_floor_ratio path on MCP and Claude Code, or update the rollback contract and tests to match supported behavior.
Docstring Coverage ⚠️ Warning Docstring coverage is 47.62% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly summarizes strict-recall calibration, filter_score configuration, override plumbing, and evaluation reports.
Out of Scope Changes check ✅ Passed The changes support [#31], including calibration, filtering configuration, interface plumbing, frontend updates, tests, reports, and documentation.
✨ 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.

@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: 15

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/hebb/mcp/server.py (1)

91-107: 📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Complete the search_memory API docstring.

search_memory is public. Its docstring omits Returns and Raises sections. Document the returned formatted result and propagated request failures.

As per coding guidelines, “Include docstring with Args, Returns, and Raises sections for all public APIs.”

🤖 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/hebb/mcp/server.py` around lines 91 - 107, Complete the public
search_memory docstring by adding Returns and Raises sections. Document that it
returns the formatted search results and that request failures propagated by the
function are raised, while preserving the existing Args documentation.

Source: Coding guidelines

🧹 Nitpick comments (3)
eval/threshold_probe.py (2)

455-475: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick win

The composite distribution is empty in every rerank run.

Line 469 appends r.score only for non-reranked entries. When top_n covers the whole result list, composite_tail has n=0. Both rerank reports show this (full-B_rerank_sigmoid_floor.json and full-C_rerank_composite_floor.json, composite_tail.n = 0). The recommended default filters on the composite score, so the probe should report the composite distribution in the same run that produces the recommendation. Collect r.composite_score for all entries.

♻️ Proposed change
     for rec in records:
         for r in rec.results:
+            comp_scores.append(r.composite_score)
             if r.is_reranked:
                 sig_scores.append(r.score)
                 if r.session_id in rec.relevant:
                     rel_sig.append(r.score)
                 else:
                     irr_sig.append(r.score)
-            else:
-                comp_scores.append(r.score)
🤖 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 `@eval/threshold_probe.py` around lines 455 - 475, Update _score_distributions
so composite_tail is populated with r.composite_score for every result,
regardless of r.is_reranked. Keep sigmoid, relevant_sigmoid, and
irrelevant_sigmoid collection scoped to reranked entries, while ensuring the
composite distribution is available for recommendation generation even when all
results were reranked.

115-176: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Hardcode of the dataset path limits reuse.

Line 117 hardcodes eval/data/locomo/locomo10.json. The probe fails when the caller runs it from another working directory, and it cannot point at a downloaded copy. Add a --data-path option and pass it through.

♻️ Proposed change
-def _load_locomo() -> list[Unit]:
+def _load_locomo(data_path: Path = Path("eval/data/locomo/locomo10.json")) -> list[Unit]:
     adapter = LoCoMoAdapter()
-    scenarios = adapter.load(Path("eval/data/locomo/locomo10.json"))
+    scenarios = adapter.load(data_path)
🤖 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 `@eval/threshold_probe.py` around lines 115 - 176, Update _load_locomo to
accept a data-path argument and use it when constructing the Path passed to
LoCoMoAdapter.load instead of hardcoding eval/data/locomo/locomo10.json. Add a
--data-path CLI option, parse it with the existing argument handling, and pass
the supplied path through to _load_locomo so callers can run from any working
directory or use downloaded data.
eval/retrieval_lab.py (1)

455-460: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

In sweep mode, empty_frac reports only the first config.

Line 457 prints empty_frac inside the if not header_done: block. Each sweep config computes its own _empty_frac, but the header shows the value of the first config only, and the reader sees it as a property of the whole sweep. Add empty_frac as a per-row column instead.

♻️ Proposed change
                 if not header_done:
-                    print(f"  units={res['_units']} q={res['_total']} empty_frac={res['_empty_frac']:.3f}")
-                    print(f"  {'config':22s} " + " ".join(f"{k:>8s}" for k in kk))
+                    print(f"  units={res['_units']} q={res['_total']}")
+                    print(f"  {'config':22s} " + " ".join(f"{k:>8s}" for k in kk) + f" {'empty':>8s}")
                     header_done = True
-                print(f"  {label:22s} " + " ".join(f"{res[k]:8.3f}" for k in kk))
+                print(f"  {label:22s} " + " ".join(f"{res[k]:8.3f}" for k in kk)
+                      + f" {res['_empty_frac']:8.3f}")
🤖 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 `@eval/retrieval_lab.py` around lines 455 - 460, Move empty_frac out of the
one-time header in the sweep output and add it as a per-row column alongside the
config metrics. Update the header and each row in the block following the
result-key collection so every config prints its own res['_empty_frac'] value,
while retaining the existing units and total metadata in the one-time header.
🤖 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 `@CHANGELOG.md`:
- Around line 20-25: Update the strict-recall threshold calibration changelog
entry to state explicitly that the shipped recall_min_score default remains 0.8
in this release, while the 0.6 value is only a recommendation from the
calibration report.

In `@eval/reports/threshold_calibration/full-C_rerank_composite_floor.json`:
- Around line 2-10: The report configuration built in main_async must identify
composite floor runs and avoid recording an unused ratio sweep. Add a floor_mode
field set to "composite" for --composite-floor reports, and record
rerank_floor_ratio_range as "N/A" in that mode while preserving the existing
range for other modes. Ensure the recorded default ratio is not misleading for
composite runs, including the current ratio field near the report’s summary
configuration.

In `@eval/reports/threshold_calibration/threshold_calibration_report.md`:
- Line 34: Update the threshold explanation at
eval/reports/threshold_calibration/threshold_calibration_report.md:34-34 to
remove the p90/93.1% percentile claim and state that the 0.5 floor exceeds the
relevant-sample median of 0.166, eliminating more than half of correct hits.
Apply the equivalent wording change at
eval/reports/threshold_calibration/threshold_calibration_report_EN.md:34-34,
removing “93.1st percentile” while preserving the same numerical conclusion.
- Around line 46-52: Correct the Mode C values in both
eval/reports/threshold_calibration/threshold_calibration_report.md#L46-L52 and
eval/reports/threshold_calibration/threshold_calibration_report_EN.md#L46-L52 to
R@1 57.1%, R@10 64.9%, and average results 2.30; in both documents, update the
Mode C R@10 values at lines 91, 124, and 133 for min_score 0.60 from 91.5% to
91.7%, matching full-C_rerank_composite_floor.json.

In `@eval/retrieval_lab.py`:
- Around line 381-385: Update the emptiness check in the retrieval loop around
_ranked_keys so it uses keys, which is assigned by both the kw_channel and
regular search branches, instead of reading resp.results. Preserve the existing
empty_count increment when keys is empty.
- Around line 377-380: Update the argument handling around args.min_score and
args.rerank_floor_ratio so mq_kwargs["rerank_floor_ratio"] is set whenever
args.rerank_floor_ratio is not None, independently of the args.min_score > 0
check. Preserve conditional inclusion of mq_kwargs["min_score"] only for
positive min_score values.

In `@eval/threshold_probe.py`:
- Around line 541-548: Update the fallback selection logic around `non_empty` so
cells with `empty_frac == 0.0` are eligible and preferred when minimizing empty
results. Remove the strict positive filter, preserve the existing
minimum-within-tolerance and `(recall_at[k], min_score)` tie-breaking behavior,
and ensure `best` is selected whenever any cell is available.
- Around line 628-635: Update _parse_range to reject non-positive step values
and ranges where stop is less than start before calculating n. Raise a clear
ValueError for either invalid condition, while preserving the existing parsing
and generated values for valid ranges.
- Around line 671-690: Update the sweep setup in the probe to read the
production defaults from Settings.recall_min_score and
Settings.rerank_floor_ratio before building min_scores and ratios, replacing the
hardcoded current_ms and current_ratio values. Ensure both defaults are inserted
into their respective grids, while preserving the existing --rerank gating for
current_ratio insertion.

In `@src/hebb/integrations/claude_code/recall.py`:
- Around line 60-64: Complete the public docstrings for handle and handle_prompt
by adding Args, Returns, and Raises sections. Document each function’s
parameters, state that both return None, and describe that operational failures
are suppressed; do not alter their behavior.

In `@src/hebb/models/memory.py`:
- Line 121: Ensure every MemorySearchResult preserves its composite score before
reranking: in src/hebb/models/memory.py lines 121-121, remove the 0.0 default
for pre_rerank_score so it cannot represent a missing value; in
src/hebb/retrieval/searcher.py lines 268-268, initialize pre_rerank_score to
disp_score when constructing each MemorySearchResult before the rerank loop.

In `@src/hebb/retrieval/searcher.py`:
- Line 286: Add a PEP 257-style docstring to the public MemorySearcher.search
method, documenting its parameters in an Args section, return value in Returns,
and possible exceptions in Raises. Keep the implementation unchanged and
describe the method’s existing behavior accurately.

In `@src/hebb/server/routers/search.py`:
- Around line 41-45: Preserve strict-recall semantics across all three call
sites: in src/hebb/server/routers/search.py lines 41-45, inject
settings.rerank_floor_ratio whenever strict_recall is enabled and the request
lacks an explicit ratio, including when min_score is provided; in
src/hebb/mcp/server.py lines 112-116 and
src/hebb/integrations/claude_code/recall.py lines 155-159, include
strict_recall=True alongside the explicit min_score request.

In `@src/hebb/static/js/components/config-section.js`:
- Around line 176-185: Update the numeric validation in the configuration save
flow around rerank_floor_ratio and recall_min_score to parse trimmed input with
Number(raw.trim()) and validate it using Number.isFinite(num), while retaining
the existing 0–1 bounds and error handling. This must reject malformed values
such as “0.5invalid” before constructing newValue or calling the API.

In `@tests/integration/server/test_api.py`:
- Around line 218-224: Strengthen the min_score integration tests in
tests/integration/server/test_api.py at lines 218-224 and 231-241: in
test_explicit_min_score_no_strengthening, assert the search returns results
before validating access counts; in
test_explicit_min_score_not_overridden_by_router, verify the stricter threshold
filters results or assert both thresholds reach the searcher unchanged, while
preserving the existing loose-versus-strict comparison.

---

Outside diff comments:
In `@src/hebb/mcp/server.py`:
- Around line 91-107: Complete the public search_memory docstring by adding
Returns and Raises sections. Document that it returns the formatted search
results and that request failures propagated by the function are raised, while
preserving the existing Args documentation.

---

Nitpick comments:
In `@eval/retrieval_lab.py`:
- Around line 455-460: Move empty_frac out of the one-time header in the sweep
output and add it as a per-row column alongside the config metrics. Update the
header and each row in the block following the result-key collection so every
config prints its own res['_empty_frac'] value, while retaining the existing
units and total metadata in the one-time header.

In `@eval/threshold_probe.py`:
- Around line 455-475: Update _score_distributions so composite_tail is
populated with r.composite_score for every result, regardless of r.is_reranked.
Keep sigmoid, relevant_sigmoid, and irrelevant_sigmoid collection scoped to
reranked entries, while ensuring the composite distribution is available for
recommendation generation even when all results were reranked.
- Around line 115-176: Update _load_locomo to accept a data-path argument and
use it when constructing the Path passed to LoCoMoAdapter.load instead of
hardcoding eval/data/locomo/locomo10.json. Add a --data-path CLI option, parse
it with the existing argument handling, and pass the supplied path through to
_load_locomo so callers can run from any working directory or use downloaded
data.
🪄 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: ce9dfc0d-0879-4ff7-912d-93c61d98e29a

📥 Commits

Reviewing files that changed from the base of the PR and between 36ce983 and e9b88d6.

📒 Files selected for processing (21)
  • CHANGELOG.md
  • eval/reports/threshold_calibration/full-A_composite_baseline.json
  • eval/reports/threshold_calibration/full-B_rerank_sigmoid_floor.json
  • eval/reports/threshold_calibration/full-C_rerank_composite_floor.json
  • eval/reports/threshold_calibration/threshold_calibration_report.md
  • eval/reports/threshold_calibration/threshold_calibration_report_EN.md
  • eval/retrieval_lab.py
  • eval/threshold_probe.py
  • src/hebb/config/settings.py
  • src/hebb/integrations/claude_code/recall.py
  • src/hebb/mcp/server.py
  • src/hebb/models/memory.py
  • src/hebb/retrieval/searcher.py
  • src/hebb/server/routers/search.py
  • src/hebb/static/js/components/activate.js
  • src/hebb/static/js/components/config-section.js
  • src/hebb/static/js/components/system.js
  • src/hebb/static/js/i18n.js
  • tests/integration/server/test_api.py
  • tests/unit/integrations/test_claude_code_hooks.py
  • tests/unit/test_audit_retrieval.py

Comment thread CHANGELOG.md Outdated
Comment on lines +2 to +10
"config": {
"dataset": "locomo",
"reranker": "BAAI/bge-reranker-base",
"top_n": 30,
"top_k": 10,
"vector": false,
"min_score_range": "0.5,0.95,0.05",
"rerank_floor_ratio_range": "0.3,1.0,0.05"
},

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🗄️ Data Integrity & Integration | 🟡 Minor | ⚡ Quick win

The recorded config does not describe the run.

Line 9 records "rerank_floor_ratio_range": "0.3,1.0,0.05", but every sweep cell in this file has "ratio": 1.0, because --composite-floor routes to _sweep_composite and that function ignores the ratio grid. Line 176 also records "ratio": 1.0 as the current default, while the production default is 0.625. A reader of this artifact cannot tell which floor mode produced the numbers.

Record the filter mode in config and set the unused range to "N/A" in composite mode. Add a field such as "floor_mode": "composite" in main_async so each committed report is self-describing.

Also applies to: 174-182

🤖 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 `@eval/reports/threshold_calibration/full-C_rerank_composite_floor.json` around
lines 2 - 10, The report configuration built in main_async must identify
composite floor runs and avoid recording an unused ratio sweep. Add a floor_mode
field set to "composite" for --composite-floor reports, and record
rerank_floor_ratio_range as "N/A" in that mode while preserving the existing
range for other modes. Ensure the recorded default ratio is not misleading for
composite runs, including the current ratio field near the report’s summary
configuration.

Comment thread eval/reports/threshold_calibration/threshold_calibration_report.md Outdated
Comment thread eval/reports/threshold_calibration/threshold_calibration_report.md Outdated
Comment thread eval/retrieval_lab.py
Comment on lines +377 to +380
if args.min_score > 0:
mq_kwargs["min_score"] = args.min_score
if args.rerank_floor_ratio is not None:
mq_kwargs["rerank_floor_ratio"] = args.rerank_floor_ratio

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Inspect the MemoryQuery defaults for min_score and rerank_floor_ratio.
rg -nP 'min_score|rerank_floor_ratio|strict_recall' --type=py -C2 src/hebb/models/memory.py

Repository: afx-team/hebb-mind

Length of output: 1468


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== eval retrieval_lab relevant lines =="
sed -n '340,390p' eval/retrieval_lab.py
echo
sed -n '488,520p' eval/retrieval_lab.py
echo

echo "== memory.py relevant default =="
sed -n '75,105p' src/hebb/models/memory.py

Repository: afx-team/hebb-mind

Length of output: 6340


Apply --rerank-floor-ratio independently of --min-score.

--min-score defaults to 0.0, which is the MemoryQuery.no filter default, so --rerank-floor-ratio 0.5 alone is currently ignored. Set mq_kwargs["rerank_floor_ratio"] whenever the flag is provided; the ratio field only translates the floor when min_score > 0, so it still does not filter alone.

🤖 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 `@eval/retrieval_lab.py` around lines 377 - 380, Update the argument handling
around args.min_score and args.rerank_floor_ratio so
mq_kwargs["rerank_floor_ratio"] is set whenever args.rerank_floor_ratio is not
None, independently of the args.min_score > 0 check. Preserve conditional
inclusion of mq_kwargs["min_score"] only for positive min_score values.

Comment on lines +60 to +64
_recall_and_print(
query=_SESSION_START_QUERY,
current_session_id=session_id,
timeout=20,
min_score=_resolve_hook_min_score(),

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Complete the public hook docstrings.

handle and handle_prompt omit Args, Returns, and Raises sections. Add the required sections, including that both functions return None and suppress operational failures.

As per coding guidelines, “All public APIs in Python MUST have docstrings with Args, Returns, and Raises sections.”

Also applies to: 85-90

🤖 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/hebb/integrations/claude_code/recall.py` around lines 60 - 64, Complete
the public docstrings for handle and handle_prompt by adding Args, Returns, and
Raises sections. Document each function’s parameters, state that both return
None, and describe that operational failures are suppressed; do not alter their
behavior.

Source: Coding guidelines

Comment thread src/hebb/models/memory.py Outdated
Comment thread src/hebb/retrieval/searcher.py Outdated
# rerank scale for the pool and keep the composite floor for the tail.
if query.min_score > 0.0:
rerank_floor = query.min_score * _RERANK_FLOOR_RATIO
rerank_floor = query.min_score * query.rerank_floor_ratio

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Add an API docstring to MemorySearcher.search.

MemorySearcher.search is public and has no docstring. Add Args, Returns, and Raises sections.

As per coding guidelines, “All public APIs in Python MUST have docstrings with Args, Returns, and Raises sections.”

🤖 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/hebb/retrieval/searcher.py` at line 286, Add a PEP 257-style docstring to
the public MemorySearcher.search method, documenting its parameters in an Args
section, return value in Returns, and possible exceptions in Raises. Keep the
implementation unchanged and describe the method’s existing behavior accurately.

Source: Coding guidelines

Comment thread src/hebb/server/routers/search.py Outdated
Comment thread tests/integration/server/test_api.py
@ch-qiaoyongheng

Copy link
Copy Markdown
Author

@coderabbitai full review

@coderabbitai

coderabbitai Bot commented Jul 31, 2026

Copy link
Copy Markdown
✅ Action performed

Full review finished.

@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: 4

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
tests/unit/test_audit_retrieval.py (1)

149-240: 📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Add return type annotations to the new test functions.

Add -> None to each new test function. This keeps the test modules compatible with strict type checking.

  • tests/unit/test_audit_retrieval.py#L149-L240: add -> None to each new async def test_* function.
  • tests/unit/integrations/test_claude_code_hooks.py#L347-L376: add -> None to test_handle_respects_hook_min_score and test_handle_defaults_to_strict_recall_when_hook_min_score_none.
  • tests/unit/integrations/test_claude_code_hooks.py#L416-L433: add -> None to test_handle_prompt_respects_hook_min_score.

As per coding guidelines, “**/*.py: Add type hints on all public functions (mypy strict is enabled in pyproject.toml).”

🤖 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 `@tests/unit/test_audit_retrieval.py` around lines 149 - 240, Add -> None
return annotations to every new async test function in
tests/unit/test_audit_retrieval.py lines 149-240. Also annotate
test_handle_respects_hook_min_score and
test_handle_defaults_to_strict_recall_when_hook_min_score_none in
tests/unit/integrations/test_claude_code_hooks.py lines 347-376, plus
test_handle_prompt_respects_hook_min_score in lines 416-433; no other changes
are needed.

Source: Coding guidelines

src/hebb/mcp/server.py (1)

96-107: 📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Complete the public search_memory docstring.

search_memory has an Args section, but it has no Returns or Raises sections. Document the formatted result and the HTTP or response-decoding errors that propagate to the caller.

Suggested docstring addition
     Args:
         query: Natural language search query.
         top_k: Maximum number of results to return (1-100, default 5).
         min_score: Optional relevance floor (0-1). When set, overrides the
             server's configured ``recall_min_score``. When omitted, strict
             recall is enabled at the server's configured floor.
+    Returns:
+        Formatted search results, or "No memories found."
+    Raises:
+        httpx.HTTPError: If the search request fails or returns a non-2xx status.
+        ValueError: If the response body is not valid JSON.
     """

As per coding guidelines, “All public APIs in Python MUST have docstrings with Args, Returns, and Raises sections.”

🤖 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/hebb/mcp/server.py` around lines 96 - 107, Complete the public
search_memory docstring by adding Returns and Raises sections. Document the
formatted search result returned to callers, and identify the HTTP errors and
response-decoding errors that can propagate from the underlying request; leave
the existing Args documentation unchanged.

Source: Coding guidelines

♻️ Duplicate comments (3)
eval/threshold_probe.py (1)

627-634: 🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win

Validate step and the range order in _parse_range.

step=0 raises ZeroDivisionError at line 633. A negative step or stop < start makes n zero or negative, so the function returns an empty list and the sweep prints an empty table with no explanation. Reject both inputs.

🛡️ Proposed fix
     start, stop, step = parts
+    if step <= 0:
+        raise ValueError(f"step must be positive — got: {step}")
+    if stop < start:
+        raise ValueError(f"stop must be >= start — got: {start},{stop}")
     n = int(round((stop - start) / step)) + 1
🤖 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 `@eval/threshold_probe.py` around lines 627 - 634, Update _parse_range to
reject a zero or negative step and any range where stop is less than start
before calculating n. Raise a clear ValueError for each invalid input, while
preserving the existing parsing and list generation for valid ascending ranges.
eval/retrieval_lab.py (1)

377-380: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Apply --rerank-floor-ratio independently of --min-score.

--min-score defaults to 0.0, so --rerank-floor-ratio 0.5 alone never reaches MemoryQuery. Set rerank_floor_ratio whenever the flag is provided. The field only translates the floor when min_score > 0, so it still does not filter on its own.

🐛 Proposed fix
                     if args.min_score > 0:
                         mq_kwargs["min_score"] = args.min_score
-                        if args.rerank_floor_ratio is not None:
-                            mq_kwargs["rerank_floor_ratio"] = args.rerank_floor_ratio
+                    if args.rerank_floor_ratio is not None:
+                        mq_kwargs["rerank_floor_ratio"] = args.rerank_floor_ratio
🤖 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 `@eval/retrieval_lab.py` around lines 377 - 380, Update the argument handling
around min_score and rerank_floor_ratio so mq_kwargs["rerank_floor_ratio"] is
set whenever args.rerank_floor_ratio is provided, regardless of args.min_score.
Keep mq_kwargs["min_score"] conditional on args.min_score > 0, preserving the
existing behavior that rerank_floor_ratio alone does not filter results.
eval/reports/threshold_calibration/full-C_rerank_composite_floor.json (1)

2-10: 🗄️ Data Integrity & Integration | 🟡 Minor | ⚡ Quick win

Both committed reports predate the probe's current config schema. main_async now always writes floor_mode and sets rerank_floor_ratio_range to "N/A" in composite mode. Neither artifact contains floor_mode, so a reader cannot tell which floor mode produced the numbers.

  • eval/reports/threshold_calibration/full-C_rerank_composite_floor.json#L2-L10: regenerate with the current probe so config contains "floor_mode": "composite" and "rerank_floor_ratio_range": "N/A", because every cell in this file uses ratio 1.0.
  • eval/reports/threshold_calibration/full-B_rerank_sigmoid_floor.json#L2-L10: regenerate so config contains "floor_mode": "sigmoid".
🤖 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 `@eval/reports/threshold_calibration/full-C_rerank_composite_floor.json` around
lines 2 - 10, Regenerate
eval/reports/threshold_calibration/full-C_rerank_composite_floor.json (lines
2-10) with the current main_async schema, adding config.floor_mode as
"composite" and setting rerank_floor_ratio_range to "N/A"; all cells use ratio
1.0. Also regenerate
eval/reports/threshold_calibration/full-B_rerank_sigmoid_floor.json (lines 2-10)
so its config includes floor_mode set to "sigmoid".
🧹 Nitpick comments (2)
CHANGELOG.md (1)

29-38: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Document the new MemoryQuery.rerank_floor_ratio field.

The Added section lists the Settings field and the frontend item, but not the per-query override. eval/retrieval_lab.py line 380 passes rerank_floor_ratio to MemoryQuery, so the field is part of the public query model. Add an entry for it, and consider listing the committed calibration reports under eval/reports/threshold_calibration/.

🤖 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 `@CHANGELOG.md` around lines 29 - 38, The CHANGELOG Added section omits the
public MemoryQuery.rerank_floor_ratio override and calibration reports. Add a
concise entry documenting MemoryQuery.rerank_floor_ratio as a per-query
override, and list the committed threshold calibration reports under
eval/reports/threshold_calibration/ if they are present.
eval/retrieval_lab.py (1)

457-460: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

empty_frac in the sweep header describes only the first config.

Line 457 prints empty_frac once, inside the header_done guard. When you combine --sweep with --min-score, each config can produce a different empty fraction, but the table shows the value from the first config only. Move empty_frac into the per-config row.

♻️ Proposed refactor
                 if not header_done:
-                    print(f"  units={res['_units']} q={res['_total']} empty_frac={res['_empty_frac']:.3f}")
-                    print(f"  {'config':22s} " + " ".join(f"{k:>8s}" for k in kk))
+                    print(f"  units={res['_units']} q={res['_total']}")
+                    print(f"  {'config':22s} " + " ".join(f"{k:>8s}" for k in kk) + f" {'empty':>8s}")
                     header_done = True
-                print(f"  {label:22s} " + " ".join(f"{res[k]:8.3f}" for k in kk))
+                print(f"  {label:22s} " + " ".join(f"{res[k]:8.3f}" for k in kk)
+                      + f" {res['_empty_frac']:8.3f}")
🤖 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 `@eval/retrieval_lab.py` around lines 457 - 460, Move the empty_frac output
from the header block guarded by header_done into the per-config print row
alongside each config’s other metrics. Update the row formatting to display
res['_empty_frac'] for every result while keeping units, total, and the shared
table header printed only once.
🤖 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 `@eval/reports/threshold_calibration/full-A_composite_baseline.json`:
- Around line 2-10: Regenerate the threshold calibration artifact using the
current probe and its main_async output format. Ensure the config includes
floor_mode, and replace the stale composite default row around ratio 0.625 with
the current current_ratio=1.0 result, including the resolved min_score 0.8
metrics instead of null values.

In `@eval/reports/threshold_calibration/threshold_calibration_report.md`:
- Line 135: Update the recommended (0.6, composite) average result count from
7.3 to 7.4 in both
eval/reports/threshold_calibration/threshold_calibration_report.md at lines
135-135 and
eval/reports/threshold_calibration/threshold_calibration_report_EN.md at lines
135-135, keeping the Chinese and English tables consistent.
- Line 89: Update the mode C empty-recall value at min_score 0.50 from 0.0% to
0.2% in eval/reports/threshold_calibration/threshold_calibration_report.md at
line 89 and
eval/reports/threshold_calibration/threshold_calibration_report_EN.md at line
89, and apply the same correction in appendix table C at line 209 in both files.

In `@src/hebb/static/js/components/config-section.js`:
- Around line 176-185: Normalize whitespace for the score fields in the
configuration save flow before validation and before constructing newValue, so
whitespace-only input is treated as empty and follows the existing null
behavior. Update the logic around the rerank_floor_ratio and recall_min_score
handling while preserving range validation for non-empty numeric values.

---

Outside diff comments:
In `@src/hebb/mcp/server.py`:
- Around line 96-107: Complete the public search_memory docstring by adding
Returns and Raises sections. Document the formatted search result returned to
callers, and identify the HTTP errors and response-decoding errors that can
propagate from the underlying request; leave the existing Args documentation
unchanged.

In `@tests/unit/test_audit_retrieval.py`:
- Around line 149-240: Add -> None return annotations to every new async test
function in tests/unit/test_audit_retrieval.py lines 149-240. Also annotate
test_handle_respects_hook_min_score and
test_handle_defaults_to_strict_recall_when_hook_min_score_none in
tests/unit/integrations/test_claude_code_hooks.py lines 347-376, plus
test_handle_prompt_respects_hook_min_score in lines 416-433; no other changes
are needed.

---

Duplicate comments:
In `@eval/reports/threshold_calibration/full-C_rerank_composite_floor.json`:
- Around line 2-10: Regenerate
eval/reports/threshold_calibration/full-C_rerank_composite_floor.json (lines
2-10) with the current main_async schema, adding config.floor_mode as
"composite" and setting rerank_floor_ratio_range to "N/A"; all cells use ratio
1.0. Also regenerate
eval/reports/threshold_calibration/full-B_rerank_sigmoid_floor.json (lines 2-10)
so its config includes floor_mode set to "sigmoid".

In `@eval/retrieval_lab.py`:
- Around line 377-380: Update the argument handling around min_score and
rerank_floor_ratio so mq_kwargs["rerank_floor_ratio"] is set whenever
args.rerank_floor_ratio is provided, regardless of args.min_score. Keep
mq_kwargs["min_score"] conditional on args.min_score > 0, preserving the
existing behavior that rerank_floor_ratio alone does not filter results.

In `@eval/threshold_probe.py`:
- Around line 627-634: Update _parse_range to reject a zero or negative step and
any range where stop is less than start before calculating n. Raise a clear
ValueError for each invalid input, while preserving the existing parsing and
list generation for valid ascending ranges.

---

Nitpick comments:
In `@CHANGELOG.md`:
- Around line 29-38: The CHANGELOG Added section omits the public
MemoryQuery.rerank_floor_ratio override and calibration reports. Add a concise
entry documenting MemoryQuery.rerank_floor_ratio as a per-query override, and
list the committed threshold calibration reports under
eval/reports/threshold_calibration/ if they are present.

In `@eval/retrieval_lab.py`:
- Around line 457-460: Move the empty_frac output from the header block guarded
by header_done into the per-config print row alongside each config’s other
metrics. Update the row formatting to display res['_empty_frac'] for every
result while keeping units, total, and the shared table header printed only
once.
🪄 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: 1a68755f-e5ad-4d1b-b60a-ef8a01a698ab

📥 Commits

Reviewing files that changed from the base of the PR and between 36ce983 and 7318a57.

📒 Files selected for processing (21)
  • CHANGELOG.md
  • eval/reports/threshold_calibration/full-A_composite_baseline.json
  • eval/reports/threshold_calibration/full-B_rerank_sigmoid_floor.json
  • eval/reports/threshold_calibration/full-C_rerank_composite_floor.json
  • eval/reports/threshold_calibration/threshold_calibration_report.md
  • eval/reports/threshold_calibration/threshold_calibration_report_EN.md
  • eval/retrieval_lab.py
  • eval/threshold_probe.py
  • src/hebb/config/settings.py
  • src/hebb/integrations/claude_code/recall.py
  • src/hebb/mcp/server.py
  • src/hebb/models/memory.py
  • src/hebb/retrieval/searcher.py
  • src/hebb/server/routers/search.py
  • src/hebb/static/js/components/activate.js
  • src/hebb/static/js/components/config-section.js
  • src/hebb/static/js/components/system.js
  • src/hebb/static/js/i18n.js
  • tests/integration/server/test_api.py
  • tests/unit/integrations/test_claude_code_hooks.py
  • tests/unit/test_audit_retrieval.py

Comment on lines +2 to +10
"config": {
"dataset": "locomo",
"reranker": "none",
"top_n": 0,
"top_k": 10,
"vector": false,
"min_score_range": "0.5,0.95,0.05",
"rerank_floor_ratio_range": "N/A"
},

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🗄️ Data Integrity & Integration | 🟡 Minor | ⚡ Quick win

This artifact does not match the probe's current output format.

main_async now always writes floor_mode into config, but lines 2-10 have no floor_mode key. Lines 174-182 record "ratio": 0.625 with every metric null, while the composite branch of the current code passes current_ratio=1.0 and would resolve the min_score 0.8 cell (R@1 0.5536, R@10 0.6355, empty_frac 0.2755). Regenerate this report with the current probe so the committed evidence matches the tool and the current-default row carries values.

Also applies to: 174-182

🤖 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 `@eval/reports/threshold_calibration/full-A_composite_baseline.json` around
lines 2 - 10, Regenerate the threshold calibration artifact using the current
probe and its main_async output format. Ensure the config includes floor_mode,
and replace the stale composite default row around ratio 0.625 with the current
current_ratio=1.0 result, including the resolved min_score 0.8 metrics instead
of null values.

| min_score | A — R@10 | A — 空召回率 | C — R@10 | C — 空召回率 | C — 平均结果数 |
| ---------- | --------- | -------- | --------- | -------- | --------- |
| 无门槛 | 91.5% | 0.0% | 94.6% | 0.0% | 10.0 |
| **0.50** | **90.4%** | **0.3%** | **93.0%** | **0.0%** | **8.7** |

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

The mode C empty-recall rate at min_score 0.50 is rounded away. full-C_rerank_composite_floor.json records empty_frac 0.0020, which is 0.2%. Both reports print 0.0%, while the neighbouring mode A value 0.0025 is printed as 0.3%. The inconsistent rounding suggests mode C never returns an empty result at this floor.

  • eval/reports/threshold_calibration/threshold_calibration_report.md#L89-L89: 将 C 模式 0.50 行的空召回率改为 0.2%,并同步修正附录表 C(第 209 行)。
  • eval/reports/threshold_calibration/threshold_calibration_report_EN.md#L89-L89: change the mode C empty recall at 0.50 to 0.2%, and apply the same correction in appendix table C at line 209.
📍 Affects 2 files
  • eval/reports/threshold_calibration/threshold_calibration_report.md#L89-L89 (this comment)
  • eval/reports/threshold_calibration/threshold_calibration_report_EN.md#L89-L89
🤖 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 `@eval/reports/threshold_calibration/threshold_calibration_report.md` at line
89, Update the mode C empty-recall value at min_score 0.50 from 0.0% to 0.2% in
eval/reports/threshold_calibration/threshold_calibration_report.md at line 89
and eval/reports/threshold_calibration/threshold_calibration_report_EN.md at
line 89, and apply the same correction in appendix table C at line 209 in both
files.

Comment thread eval/reports/threshold_calibration/threshold_calibration_report.md
Comment on lines +176 to +185
const raw = input.type === 'checkbox' ? String(input.checked) : input.value;
// Frontend validation for 0-1 numeric fields before hitting the API.
if ((key === 'rerank_floor_ratio' || key === 'recall_min_score') && raw !== '') {
const num = Number(raw.trim());
if (!Number.isFinite(num) || num < 0 || num > 1) {
error(t('settings.error.range_0_1', { key }));
return;
}
}
const newValue = raw || 'null';

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Handle whitespace-only score input before validation.

For input such as " ", raw !== '' is true. Number(raw.trim()) becomes 0, so validation passes. raw || 'null' then sends the whitespace string to the API.

Normalize score fields before the empty check and before constructing newValue.

Suggested fix
         const raw = input.type === 'checkbox' ? String(input.checked) : input.value;
+        const isScoreKey = key === 'rerank_floor_ratio' || key === 'recall_min_score';
+        const normalizedRaw = raw.trim();
         // Frontend validation for 0-1 numeric fields before hitting the API.
-        if ((key === 'rerank_floor_ratio' || key === 'recall_min_score') && raw !== '') {
-          const num = Number(raw.trim());
+        if (isScoreKey && normalizedRaw !== '') {
+          const num = Number(normalizedRaw);
           if (!Number.isFinite(num) || num < 0 || num > 1) {
             error(t('settings.error.range_0_1', { key }));
             return;
           }
         }
-        const newValue = raw || 'null';
+        const newValue = (isScoreKey ? normalizedRaw : raw) || 'null';
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const raw = input.type === 'checkbox' ? String(input.checked) : input.value;
// Frontend validation for 0-1 numeric fields before hitting the API.
if ((key === 'rerank_floor_ratio' || key === 'recall_min_score') && raw !== '') {
const num = Number(raw.trim());
if (!Number.isFinite(num) || num < 0 || num > 1) {
error(t('settings.error.range_0_1', { key }));
return;
}
}
const newValue = raw || 'null';
const raw = input.type === 'checkbox' ? String(input.checked) : input.value;
const isScoreKey = key === 'rerank_floor_ratio' || key === 'recall_min_score';
const normalizedRaw = raw.trim();
// Frontend validation for 0-1 numeric fields before hitting the API.
if (isScoreKey && normalizedRaw !== '') {
const num = Number(normalizedRaw);
if (!Number.isFinite(num) || num < 0 || num > 1) {
error(t('settings.error.range_0_1', { key }));
return;
}
}
const newValue = (isScoreKey ? normalizedRaw : raw) || 'null';
🤖 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/hebb/static/js/components/config-section.js` around lines 176 - 185,
Normalize whitespace for the score fields in the configuration save flow before
validation and before constructing newValue, so whitespace-only input is treated
as empty and follows the existing null behavior. Update the logic around the
rerank_floor_ratio and recall_min_score handling while preserving range
validation for non-empty numeric values.

@ch-qiaoyongheng

Copy link
Copy Markdown
Author

#31

@ch-qiaoyongheng ch-qiaoyongheng changed the title feat(retrieval): calibrate strict-recall floor, make rerank_floor_ratio configurable, plumb min_score through MCP/hook, add full eval report feat(retrieval): Calibrate strict-recall threshold, add filter_score config, pass through min_score via MCP/hook, and add full evaluation report Aug 2, 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: 5

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/hebb/config/settings.py (1)

172-178: 📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Correct the recall_hook_min_score description.

The recall hook sends this value as filter_score, not min_score. The fallback is the composite-score filter configuration, not recall_min_score. This text can cause operators to configure the wrong threshold when the two settings differ.

Proposed fix
-        description="Per-deployment min_score override for the recall hook; "
-        "None = use strict_recall (global recall_min_score)",
+        description="Per-deployment composite-score filter override for the recall hook; "
+        "None = use the global filter_score",
🤖 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/hebb/config/settings.py` around lines 172 - 178, Update the description
for the recall_hook_min_score field in the settings configuration to state that
the value is sent as filter_score and that None falls back to the
composite-score filter configuration, replacing the incorrect min_score and
recall_min_score references.
♻️ Duplicate comments (1)
src/hebb/static/js/components/config-section.js (1)

176-185: 🎯 Functional Correctness | 🟡 Minor

Normalize whitespace-only filter_score input before validation.

At Line 178, raw !== '' is true for " ". Number(raw.trim()) becomes 0, so validation passes. At Line 185, raw || 'null' sends the whitespace string instead of null. Normalize the score before the empty check and before creating newValue.

This is the same whitespace-validation defect previously reported for the legacy score fields, now present on filter_score.

Suggested fix
         const raw = input.type === 'checkbox' ? String(input.checked) : input.value;
+        const normalizedRaw = key === 'filter_score' ? raw.trim() : raw;
         // Frontend validation for 0-1 numeric fields before hitting the API.
-        if (key === 'filter_score' && raw !== '') {
-          const num = Number(raw.trim());
+        if (key === 'filter_score' && normalizedRaw !== '') {
+          const num = Number(normalizedRaw);
           if (!Number.isFinite(num) || num < 0 || num > 1) {
             error(t('settings.error.range_0_1', { key }));
             return;
           }
         }
-        const newValue = raw || 'null';
+        const newValue = normalizedRaw || 'null';
🤖 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/hebb/static/js/components/config-section.js` around lines 176 - 185,
Normalize the `filter_score` value by trimming whitespace before the empty check
and before constructing `newValue`. Use the normalized value for numeric
validation and ensure whitespace-only input is treated as empty so it is sent as
`null`, while preserving existing validation for non-empty scores.
🤖 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 `@src/hebb/integrations/claude_code/recall.py`:
- Around line 56-68: Update _resolve_hook_filter_score so recall_hook_min_score
is not returned as filter_score; preserve filter_score as the sole
composite-score override and return it when configured, otherwise None. Route
recall_hook_min_score through the hook’s min_score rollback path or an
established migration/alias mechanism instead.

In `@src/hebb/mcp/server.py`:
- Around line 105-111: Complete the public search_memory docstring by adding
Returns and Raises sections alongside its existing Args section. Describe the
formatted string returned by the tool and document the request and response
errors callers must handle, using the function’s existing exception behavior and
symbols rather than inventing new error types.

In `@src/hebb/retrieval/searcher.py`:
- Around line 284-302: Update the preferred filter in the query retrieval flow
to compare each result’s pre_rerank_score against query.filter_score instead of
score, including the comparison inside the results loop. Keep score unchanged
for final reranked ordering, and leave the legacy min_score fallback behavior
intact.

In `@src/hebb/server/routers/search.py`:
- Around line 41-45: Update the public search_memories docstring to document the
precedence among filter_score, min_score, and strict_recall, including that an
explicitly provided filter_score overrides the strict_recall default. Add
complete Args, Returns, and Raises sections describing the method’s parameters,
result, and possible exceptions.
- Around line 41-45: Update the strict-recall filter-score injection condition
in the query handling logic so settings.filter_score is applied only when
neither filter_score nor the legacy min_score is explicitly supplied. Preserve
caller-provided min_score and filter_score values unchanged.

---

Outside diff comments:
In `@src/hebb/config/settings.py`:
- Around line 172-178: Update the description for the recall_hook_min_score
field in the settings configuration to state that the value is sent as
filter_score and that None falls back to the composite-score filter
configuration, replacing the incorrect min_score and recall_min_score
references.

---

Duplicate comments:
In `@src/hebb/static/js/components/config-section.js`:
- Around line 176-185: Normalize the `filter_score` value by trimming whitespace
before the empty check and before constructing `newValue`. Use the normalized
value for numeric validation and ensure whitespace-only input is treated as
empty so it is sent as `null`, while preserving existing validation for
non-empty scores.
🪄 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: 3fb9df99-513f-4736-af18-98c731e4c4f0

📥 Commits

Reviewing files that changed from the base of the PR and between 7318a57 and 77bec46.

📒 Files selected for processing (12)
  • CHANGELOG.md
  • eval/reports/threshold_calibration/threshold_calibration_report.md
  • src/hebb/config/settings.py
  • src/hebb/integrations/claude_code/recall.py
  • src/hebb/mcp/server.py
  • src/hebb/models/memory.py
  • src/hebb/retrieval/searcher.py
  • src/hebb/server/routers/search.py
  • src/hebb/static/js/components/activate.js
  • src/hebb/static/js/components/config-section.js
  • src/hebb/static/js/i18n.js
  • tests/unit/integrations/test_claude_code_hooks.py
🚧 Files skipped from review as they are similar to previous changes (3)
  • tests/unit/integrations/test_claude_code_hooks.py
  • src/hebb/static/js/i18n.js
  • eval/reports/threshold_calibration/threshold_calibration_report.md

Comment on lines +56 to +68
def _resolve_hook_filter_score() -> float | None:
"""Return the per-deployment filter_score override, or None for default.

Priority: recall_hook_min_score > filter_score > None (strict_recall path).
"""
try:
settings = load_settings()
# If recall_hook_min_score is explicitly set, use it as filter_score
if settings.recall_hook_min_score is not None:
return settings.recall_hook_min_score
return settings.filter_score
except Exception:
return None

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🗄️ Data Integrity & Integration | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail
rg -n -C 4 'recall_hook_min_score|filter_score|min_score' src tests

Repository: afx-team/hebb-mind

Length of output: 50374


Don’t map recall_hook_min_score directly to filter_score.

recall_hook_min_score is documented as the per-deployment min_score override, while filter_score is the composite-score filter. Since an existing hook config with recall_hook_min_score=0.7 currently sends filter_score=0.7, it changes the legacy override from the old dual-scale behavior to direct composite filtering. Send this value to min_score only for rollback, or add a migration/alias if this was intentional.

🤖 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/hebb/integrations/claude_code/recall.py` around lines 56 - 68, Update
_resolve_hook_filter_score so recall_hook_min_score is not returned as
filter_score; preserve filter_score as the sole composite-score override and
return it when configured, otherwise None. Route recall_hook_min_score through
the hook’s min_score rollback path or an established migration/alias mechanism
instead.

Comment thread src/hebb/mcp/server.py Outdated
Comment thread src/hebb/retrieval/searcher.py
Comment thread src/hebb/server/routers/search.py Outdated
@ch-qiaoyongheng

Copy link
Copy Markdown
Author

https://github.com/coderabbitai full review

@ch-qiaoyongheng

Copy link
Copy Markdown
Author

@coderabbitai full review

@coderabbitai

coderabbitai Bot commented Aug 2, 2026

Copy link
Copy Markdown
✅ Action performed

Full review finished.


Your included review limit is currently reached under our Fair Usage Limits Policy. This review may still proceed through usage-based billing if eligible. Your next included review will be available in 9 minutes.

@ch-qiaoyongheng

Copy link
Copy Markdown
Author

@coderabbitai full review

@coderabbitai

coderabbitai Bot commented Aug 2, 2026

Copy link
Copy Markdown
✅ Action performed

Full review finished.

@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: 3

♻️ Duplicate comments (5)
eval/threshold_probe.py (1)

627-634: 🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win

_parse_range still accepts an invalid step.

A step of 0 raises ZeroDivisionError at line 633. A negative step or stop < start makes n zero or negative, and the function returns an empty list. The sweep then produces no cells and the probe prints an empty table with no explanation. This was raised in a previous review and is still present.

🛡️ Proposed fix
     start, stop, step = parts
+    if step <= 0:
+        raise ValueError(f"step must be positive — got: {step}")
+    if stop < start:
+        raise ValueError(f"stop must be >= start — got: {start},{stop}")
     n = int(round((stop - start) / step)) + 1
🤖 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 `@eval/threshold_probe.py` around lines 627 - 634, Validate inputs in
_parse_range before calculating n: reject step values less than or equal to
zero, and reject ranges where stop is less than start, by raising a clear
ValueError. Preserve the existing parsing and list-generation behavior for valid
ascending ranges.
eval/reports/threshold_calibration/threshold_calibration_report.md (2)

135-135: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

The comparison table in both reports still states 7.3 average results. Sections 3.2 and 4.2 in both documents state 7.4, and full-C_rerank_composite_floor.json records mean_results 7.36 at min_score 0.6. Only the two-approach comparison table was left at 7.3.

  • eval/reports/threshold_calibration/threshold_calibration_report.md#L135-L135: 将"推荐方案 (0.6, composite)"列的平均结果数从 7.3 改为 7.4。
  • eval/reports/threshold_calibration/threshold_calibration_report_EN.md#L135-L135: change the avg results value for "Recommended (0.6, composite)" from 7.3 to 7.4.
🤖 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 `@eval/reports/threshold_calibration/threshold_calibration_report.md` at line
135, Update the comparison table’s recommended composite approach
average-results value from 7.3 to 7.4 in
eval/reports/threshold_calibration/threshold_calibration_report.md at lines
135-135 and
eval/reports/threshold_calibration/threshold_calibration_report_EN.md at lines
135-135; make no other changes.

89-90: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Both language reports misprint two mode C empty-recall values. full-C_rerank_composite_floor.json records empty_frac 0.0020 at min_score 0.50 and 0.0046 at min_score 0.55, which are 0.2% and 0.5%. Both documents print 0.0% and 0.1%. The neighbouring mode A value 0.0025 is already printed as 0.3%, so this is not consistent rounding.

  • eval/reports/threshold_calibration/threshold_calibration_report.md#L89-L90: 将 C 模式 0.50 行改为 0.2%、0.55 行改为 0.5%,并同步修正附录表 C 第 209 行。
  • eval/reports/threshold_calibration/threshold_calibration_report_EN.md#L89-L90: set the mode C 0.50 empty recall to 0.2% and the 0.55 empty recall to 0.5%, and apply the same correction in appendix table C at line 209.
🤖 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 `@eval/reports/threshold_calibration/threshold_calibration_report.md` around
lines 89 - 90, The mode C empty-recall values are misprinted in both language
reports. Update
eval/reports/threshold_calibration/threshold_calibration_report.md lines 89-90
and eval/reports/threshold_calibration/threshold_calibration_report_EN.md lines
89-90 to show 0.2% at min_score 0.50 and 0.5% at 0.55, and apply the same
corrections to appendix table C at line 209 in both files.
eval/retrieval_lab.py (1)

377-380: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

--rerank-floor-ratio is still ignored unless --min-score is positive.

Line 377 gates both keyword arguments on args.min_score > 0. --min-score defaults to 0.0, so --rerank-floor-ratio 0.5 alone never reaches MemoryQuery. Set rerank_floor_ratio whenever the flag is provided. The ratio only scales the floor when min_score > 0, so it still cannot filter on its own. This repeats a previous review comment.

🐛 Proposed fix
                     if args.min_score > 0:
                         mq_kwargs["min_score"] = args.min_score
-                        if args.rerank_floor_ratio is not None:
-                            mq_kwargs["rerank_floor_ratio"] = args.rerank_floor_ratio
+                    if args.rerank_floor_ratio is not None:
+                        mq_kwargs["rerank_floor_ratio"] = args.rerank_floor_ratio
🤖 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 `@eval/retrieval_lab.py` around lines 377 - 380, Update the
argument-to-mq_kwargs handling in the retrieval query setup so
rerank_floor_ratio is added whenever args.rerank_floor_ratio is not None,
independently of args.min_score. Keep min_score gated by args.min_score > 0,
preserving the behavior that rerank_floor_ratio alone does not filter results.
eval/reports/threshold_calibration/full-C_rerank_composite_floor.json (1)

2-10: 🗄️ Data Integrity & Integration | 🟡 Minor | ⚡ Quick win

All three committed reports predate the current probe output format. main_async in eval/threshold_probe.py now writes floor_mode into config and sets rerank_floor_ratio_range to "N/A" in composite mode. No committed artifact carries floor_mode, so none of them is self-describing. Regenerate all three with the current probe.

  • eval/reports/threshold_calibration/full-C_rerank_composite_floor.json#L2-L10: regenerate so config gains floor_mode: "composite" and rerank_floor_ratio_range becomes "N/A" instead of the unused "0.3,1.0,0.05" grid.
  • eval/reports/threshold_calibration/full-A_composite_baseline.json#L174-L182: regenerate after fixing the default-resolution logic at eval/threshold_probe.py lines 670-690, so the current_default block carries values instead of null.
  • eval/reports/threshold_calibration/full-B_rerank_sigmoid_floor.json#L2-L10: regenerate so config gains floor_mode: "sigmoid".
🤖 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 `@eval/reports/threshold_calibration/full-C_rerank_composite_floor.json` around
lines 2 - 10, Regenerate all three reports using the current
eval/threshold_probe.py output: update
eval/reports/threshold_calibration/full-C_rerank_composite_floor.json lines 2-10
with floor_mode "composite" and rerank_floor_ratio_range "N/A"; regenerate
eval/reports/threshold_calibration/full-A_composite_baseline.json lines 174-182
after fixing main_async’s default-resolution logic around lines 670-690 so
current_default contains values rather than null; and update
eval/reports/threshold_calibration/full-B_rerank_sigmoid_floor.json lines 2-10
with floor_mode "sigmoid".
🧹 Nitpick comments (1)
eval/retrieval_lab.py (1)

383-385: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick win

empty_frac measures missing metadata keys, not empty results.

_ranked_keys drops any result whose metadata has no session_id (or no sid/global_idx). It also folds in resp.related. A query that returns results without the metric key is therefore counted as empty, and a query whose only output is related memories is counted as non-empty. eval/threshold_probe.py counts emptiness from resp.results alone, so the two tools report different quantities under the same name.

Track both counters if you want the numbers to be comparable.

♻️ Proposed change
                 total += 1
-                if not keys:
+                if not keys:
                     empty_count += 1
+                if not kw_channel and not resp.results:
+                    no_result_count += 1
🤖 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 `@eval/retrieval_lab.py` around lines 383 - 385, Update the evaluation loop
around _ranked_keys to distinguish empty retrieval results from missing metric
keys: determine the empty-results count from resp.results alone, while
separately counting queries whose results lack usable session_id/sid/global_idx
metadata and those represented only by resp.related. Preserve total and expose
both counters so empty_frac matches threshold_probe and missing-key behavior
remains measurable.
🤖 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 `@CHANGELOG.md`:
- Line 26: Update the changelog entry for the recommended filter_score=0.6
configuration to report R@10 as 91.7%, matching the calibration reports; do not
use the 91.5% unfiltered baseline value.

In `@eval/reports/threshold_calibration/threshold_calibration_report.md`:
- Around line 124-125: Update both
eval/reports/threshold_calibration/threshold_calibration_report.md (lines 19,
124-125, and 164) and
eval/reports/threshold_calibration/threshold_calibration_report_EN.md (lines 19,
124-125, and 164): replace the recommended 0.6 setting name with filter_score,
and explicitly state that recall_min_score is deprecated in the
backward-compatibility notes.

In `@src/hebb/config/settings.py`:
- Around line 173-178: Update the description on the recall_hook_min_score Field
to accurately document that None makes the recall hook send
settings.filter_score with strict_recall=True, while a configured value is
mapped to filter_score; remove the incorrect reference to recall_min_score.

---

Duplicate comments:
In `@eval/reports/threshold_calibration/full-C_rerank_composite_floor.json`:
- Around line 2-10: Regenerate all three reports using the current
eval/threshold_probe.py output: update
eval/reports/threshold_calibration/full-C_rerank_composite_floor.json lines 2-10
with floor_mode "composite" and rerank_floor_ratio_range "N/A"; regenerate
eval/reports/threshold_calibration/full-A_composite_baseline.json lines 174-182
after fixing main_async’s default-resolution logic around lines 670-690 so
current_default contains values rather than null; and update
eval/reports/threshold_calibration/full-B_rerank_sigmoid_floor.json lines 2-10
with floor_mode "sigmoid".

In `@eval/reports/threshold_calibration/threshold_calibration_report.md`:
- Line 135: Update the comparison table’s recommended composite approach
average-results value from 7.3 to 7.4 in
eval/reports/threshold_calibration/threshold_calibration_report.md at lines
135-135 and
eval/reports/threshold_calibration/threshold_calibration_report_EN.md at lines
135-135; make no other changes.
- Around line 89-90: The mode C empty-recall values are misprinted in both
language reports. Update
eval/reports/threshold_calibration/threshold_calibration_report.md lines 89-90
and eval/reports/threshold_calibration/threshold_calibration_report_EN.md lines
89-90 to show 0.2% at min_score 0.50 and 0.5% at 0.55, and apply the same
corrections to appendix table C at line 209 in both files.

In `@eval/retrieval_lab.py`:
- Around line 377-380: Update the argument-to-mq_kwargs handling in the
retrieval query setup so rerank_floor_ratio is added whenever
args.rerank_floor_ratio is not None, independently of args.min_score. Keep
min_score gated by args.min_score > 0, preserving the behavior that
rerank_floor_ratio alone does not filter results.

In `@eval/threshold_probe.py`:
- Around line 627-634: Validate inputs in _parse_range before calculating n:
reject step values less than or equal to zero, and reject ranges where stop is
less than start, by raising a clear ValueError. Preserve the existing parsing
and list-generation behavior for valid ascending ranges.

---

Nitpick comments:
In `@eval/retrieval_lab.py`:
- Around line 383-385: Update the evaluation loop around _ranked_keys to
distinguish empty retrieval results from missing metric keys: determine the
empty-results count from resp.results alone, while separately counting queries
whose results lack usable session_id/sid/global_idx metadata and those
represented only by resp.related. Preserve total and expose both counters so
empty_frac matches threshold_probe and missing-key behavior remains measurable.
🪄 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: d367dbac-b654-44bb-a763-7715683456a8

📥 Commits

Reviewing files that changed from the base of the PR and between 36ce983 and a7a2574.

📒 Files selected for processing (21)
  • CHANGELOG.md
  • eval/reports/threshold_calibration/full-A_composite_baseline.json
  • eval/reports/threshold_calibration/full-B_rerank_sigmoid_floor.json
  • eval/reports/threshold_calibration/full-C_rerank_composite_floor.json
  • eval/reports/threshold_calibration/threshold_calibration_report.md
  • eval/reports/threshold_calibration/threshold_calibration_report_EN.md
  • eval/retrieval_lab.py
  • eval/threshold_probe.py
  • src/hebb/config/settings.py
  • src/hebb/integrations/claude_code/recall.py
  • src/hebb/mcp/server.py
  • src/hebb/models/memory.py
  • src/hebb/retrieval/searcher.py
  • src/hebb/server/routers/search.py
  • src/hebb/static/js/components/activate.js
  • src/hebb/static/js/components/config-section.js
  • src/hebb/static/js/components/system.js
  • src/hebb/static/js/i18n.js
  • tests/integration/server/test_api.py
  • tests/unit/integrations/test_claude_code_hooks.py
  • tests/unit/test_audit_retrieval.py

Comment thread CHANGELOG.md Outdated
Comment thread eval/reports/threshold_calibration/threshold_calibration_report.md
Comment thread src/hebb/config/settings.py Outdated
@ch-qiaoyongheng

Copy link
Copy Markdown
Author

@coderabbitai full review

@coderabbitai

coderabbitai Bot commented Aug 2, 2026

Copy link
Copy Markdown
✅ Action performed

Full review finished.

@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: 4

♻️ Duplicate comments (6)
eval/reports/threshold_calibration/threshold_calibration_report.md (2)

135-135: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

The recommended-configuration avg-results figure was not actually corrected.

Line 135 states 7.3 average results for "推荐方案 (0.6, composite)", but full-C_rerank_composite_floor.json records mean_results 7.36 (rounds to 7.4) at min_score 0.6, and Line 124 on the same page already states "平均 7.4 条" for the same configuration. A prior review flagged this exact line and it was marked as addressed, but the value shown here is still the old, incorrect one.

🐛 Proposed fix
-| 平均结果数 | 1.4                 | 7.3                   | 8.7                   |
+| 平均结果数 | 1.4                 | 7.4                   | 8.7                   |
🤖 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 `@eval/reports/threshold_calibration/threshold_calibration_report.md` at line
135, Update the recommended configuration’s average-results value in the report
table from 7.3 to 7.4, matching the 7.36 mean_results value and the existing “平均
7.4 条” statement for the same configuration.

89-90: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Mode C empty-recall values do not match the source data.

full-C_rerank_composite_floor.json records empty_frac 0.0020 (0.2%) at min_score 0.50 and empty_frac 0.0046 (0.5% rounded) at min_score 0.55. Lines 89 and 90 both print the C column as effectively rounded to 0.0%/0.1%, and the appendix at line 209 repeats the 0.50 error. The neighboring A column at the same rows rounds correctly (0.3%, 0.5%), so this looks like a transcription slip rather than intentional rounding.

🐛 Proposed fix
-| **0.50**   | **90.4%** | **0.3%** | **93.0%** | **0.0%** | **8.7**   |
-| 0.55       | 90.0%     | 0.5%     | 92.8%     | 0.1%     | 8.1       |
+| **0.50**   | **90.4%** | **0.3%** | **93.0%** | **0.2%** | **8.7**   |
+| 0.55       | 90.0%     | 0.5%     | 92.8%     | 0.5%     | 8.1       |
-| 0.50      | 72.9% | 87.0% | 90.7% | 93.0% | 0.0%  | 8.7   |
+| 0.50      | 72.9% | 87.0% | 90.7% | 93.0% | 0.2%  | 8.7   |

Also applies to: 209-209

🤖 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 `@eval/reports/threshold_calibration/threshold_calibration_report.md` around
lines 89 - 90, Update the Mode C empty-recall values in the threshold table and
its appendix entry to match full-C_rerank_composite_floor.json: use 0.2% for
min_score 0.50 and 0.5% for min_score 0.55, preserving the neighboring columns
and existing formatting.
eval/reports/threshold_calibration/full-C_rerank_composite_floor.json (1)

2-10: 🗄️ Data Integrity & Integration | 🟡 Minor | ⚡ Quick win

Config still misrepresents this run.

Every sweep cell in this file has "ratio": 1.0 because --composite-floor routes to _sweep_composite, which ignores the ratio grid, yet line 9 still records "rerank_floor_ratio_range": "0.3,1.0,0.05". The current probe code sets this field to "N/A" for composite mode and adds a floor_mode key, but neither change is reflected in this committed artifact.

🐛 Proposed fix
   "config": {
     "dataset": "locomo",
+    "floor_mode": "composite",
     "reranker": "BAAI/bge-reranker-base",
     "top_n": 30,
     "top_k": 10,
     "vector": false,
     "min_score_range": "0.5,0.95,0.05",
-    "rerank_floor_ratio_range": "0.3,1.0,0.05"
+    "rerank_floor_ratio_range": "N/A"
   },
🤖 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 `@eval/reports/threshold_calibration/full-C_rerank_composite_floor.json` around
lines 2 - 10, Update the committed composite-mode calibration artifact
configuration to match the probe output: set rerank_floor_ratio_range to "N/A"
and add the floor_mode key indicating composite mode. Preserve the existing
dataset, reranker, and sweep settings.
eval/retrieval_lab.py (1)

372-380: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Apply --rerank-floor-ratio independently of --min-score.

Line 379 sets mq_kwargs["rerank_floor_ratio"] only when args.min_score > 0. MemoryQuery.min_score has its own non-zero production default, so omitting min_score from mq_kwargs does not mean "no floor" — the searcher still applies the model's default floor. --rerank-floor-ratio 0.5 alone (without --min-score) is currently silently ignored even though it should still affect that default floor.

🐛 Proposed fix
                     if args.min_score > 0:
                         mq_kwargs["min_score"] = args.min_score
-                        if args.rerank_floor_ratio is not None:
-                            mq_kwargs["rerank_floor_ratio"] = args.rerank_floor_ratio
+                    if args.rerank_floor_ratio is not None:
+                        mq_kwargs["rerank_floor_ratio"] = args.rerank_floor_ratio
🤖 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 `@eval/retrieval_lab.py` around lines 372 - 380, Update the mq_kwargs
construction so args.rerank_floor_ratio is added whenever it is not None,
independently of the args.min_score > 0 condition. Keep args.min_score handling
unchanged, while ensuring a standalone --rerank-floor-ratio overrides the
searcher’s default floor.
eval/threshold_probe.py (1)

627-634: 🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win

Validate step and range direction in _parse_range.

step=0 raises ZeroDivisionError at Line 633. A negative step or stop < start produces n <= 0 and an empty list, and the sweep then silently prints an empty table. Reject a non-positive step and an inverted range before computing n.

🛡️ Proposed fix
     start, stop, step = parts
+    if step <= 0:
+        raise ValueError(f"step must be positive — got: {step}")
+    if stop < start:
+        raise ValueError(f"stop must be >= start — got: {start},{stop}")
     n = int(round((stop - start) / step)) + 1
🤖 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 `@eval/threshold_probe.py` around lines 627 - 634, Update _parse_range to
validate that step is positive and stop is not less than start before
calculating n. Raise ValueError for invalid step or inverted ranges, while
preserving the existing parsing and range generation behavior for valid inputs.
eval/reports/threshold_calibration/full-A_composite_baseline.json (1)

2-10: 🗄️ Data Integrity & Integration | 🟡 Minor | ⚡ Quick win

Regenerate this artifact with the current probe.

main_async now always writes floor_mode into config, but lines 2-10 have no floor_mode key. Lines 174-182 still record "ratio": 0.625 with every metric null, while the composite branch of the current code passes current_ratio=1.0 and would resolve the min_score=0.8 cell (R@1 0.5536, R@10 0.6355, empty_frac 0.2755) from the sweep at lines 123-132. Regenerate this report with the current probe so the committed evidence matches the tool and the current-default row carries values.

🐛 Proposed fix (until regenerated)
   "config": {
     "dataset": "locomo",
+    "floor_mode": "composite",
     "reranker": "none",
   "current_default": {
     "min_score": 0.8,
-    "ratio": 0.625,
-    "R@1": null,
-    "R@3": null,
-    "R@5": null,
-    "R@10": null,
-    "empty_frac": null
+    "ratio": 1.0,
+    "R@1": 0.5536,
+    "R@3": 0.6158,
+    "R@5": 0.6289,
+    "R@10": 0.6355,
+    "empty_frac": 0.2755
   }

Also applies to: 174-182

🤖 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 `@eval/reports/threshold_calibration/full-A_composite_baseline.json` around
lines 2 - 10, Regenerate full-A_composite_baseline.json using the current probe
and commit the refreshed artifact. Ensure the config includes the floor_mode
field written by main_async, and update the current-default composite result
from the stale ratio 0.625/null metrics to the current_ratio=1.0 result resolved
from the sweep, including its corresponding metrics.
🤖 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 `@eval/reports/threshold_calibration/full-B_rerank_sigmoid_floor.json`:
- Around line 2-10: Add the missing floor_mode configuration entry to the
artifact’s config, setting it to "sigmoid" to match the probe mode that produced
these results. Preserve the existing dataset, reranker, and threshold settings.

In `@eval/reports/threshold_calibration/threshold_calibration_report_EN.md`:
- Around line 89-90: Update the Mode C empty-recall values in the threshold
calibration report to match full-C_rerank_composite_floor.json: use 0.2% for
min_score 0.50 and 0.5% for 0.55, and correct the repeated 0.50 value in the
appendix.
- Line 135: Update the “Recommended (0.6, composite)” Avg Results value in the
report table from 7.3 to 7.4, matching the 7.36 mean_results value in
full-C_rerank_composite_floor.json rounded to one decimal place.

In `@src/hebb/config/settings.py`:
- Around line 133-145: The deprecated recall_min_score setting is documented as
a rollback switch but is not consumed by the search path. Add an explicit
rollback branch in the supplied search_memories flow that uses configured
recall_min_score for legacy filtering, and add an integration test proving that
changing the rollback value changes results; otherwise remove the setting and
its rollback documentation.

---

Duplicate comments:
In `@eval/reports/threshold_calibration/full-A_composite_baseline.json`:
- Around line 2-10: Regenerate full-A_composite_baseline.json using the current
probe and commit the refreshed artifact. Ensure the config includes the
floor_mode field written by main_async, and update the current-default composite
result from the stale ratio 0.625/null metrics to the current_ratio=1.0 result
resolved from the sweep, including its corresponding metrics.

In `@eval/reports/threshold_calibration/full-C_rerank_composite_floor.json`:
- Around line 2-10: Update the committed composite-mode calibration artifact
configuration to match the probe output: set rerank_floor_ratio_range to "N/A"
and add the floor_mode key indicating composite mode. Preserve the existing
dataset, reranker, and sweep settings.

In `@eval/reports/threshold_calibration/threshold_calibration_report.md`:
- Line 135: Update the recommended configuration’s average-results value in the
report table from 7.3 to 7.4, matching the 7.36 mean_results value and the
existing “平均 7.4 条” statement for the same configuration.
- Around line 89-90: Update the Mode C empty-recall values in the threshold
table and its appendix entry to match full-C_rerank_composite_floor.json: use
0.2% for min_score 0.50 and 0.5% for min_score 0.55, preserving the neighboring
columns and existing formatting.

In `@eval/retrieval_lab.py`:
- Around line 372-380: Update the mq_kwargs construction so
args.rerank_floor_ratio is added whenever it is not None, independently of the
args.min_score > 0 condition. Keep args.min_score handling unchanged, while
ensuring a standalone --rerank-floor-ratio overrides the searcher’s default
floor.

In `@eval/threshold_probe.py`:
- Around line 627-634: Update _parse_range to validate that step is positive and
stop is not less than start before calculating n. Raise ValueError for invalid
step or inverted ranges, while preserving the existing parsing and range
generation behavior for valid inputs.
🪄 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: 64e8d5c1-8085-47c2-819d-3b394c969430

📥 Commits

Reviewing files that changed from the base of the PR and between 36ce983 and c804158.

📒 Files selected for processing (21)
  • CHANGELOG.md
  • eval/reports/threshold_calibration/full-A_composite_baseline.json
  • eval/reports/threshold_calibration/full-B_rerank_sigmoid_floor.json
  • eval/reports/threshold_calibration/full-C_rerank_composite_floor.json
  • eval/reports/threshold_calibration/threshold_calibration_report.md
  • eval/reports/threshold_calibration/threshold_calibration_report_EN.md
  • eval/retrieval_lab.py
  • eval/threshold_probe.py
  • src/hebb/config/settings.py
  • src/hebb/integrations/claude_code/recall.py
  • src/hebb/mcp/server.py
  • src/hebb/models/memory.py
  • src/hebb/retrieval/searcher.py
  • src/hebb/server/routers/search.py
  • src/hebb/static/js/components/activate.js
  • src/hebb/static/js/components/config-section.js
  • src/hebb/static/js/components/system.js
  • src/hebb/static/js/i18n.js
  • tests/integration/server/test_api.py
  • tests/unit/integrations/test_claude_code_hooks.py
  • tests/unit/test_audit_retrieval.py

Comment thread eval/reports/threshold_calibration/full-B_rerank_sigmoid_floor.json
Comment thread eval/reports/threshold_calibration/threshold_calibration_report_EN.md Outdated
Comment thread eval/reports/threshold_calibration/threshold_calibration_report_EN.md Outdated
Comment thread src/hebb/config/settings.py Outdated
Comment on lines +133 to +145
# DEPRECATED: This field is retained only as an emergency rollback switch.
# New code should use ``filter_score`` for composite-score filtering instead.
# The router no longer reads this field; it was replaced by filter_score
# after eval (LoCoMo, 1978 queries) proved sigmoid scores unsuitable for
# hard filtering. See: eval/reports/threshold_calibration/threshold_calibration_review_v2.md
recall_min_score: float = Field(
default=0.8,
default=0.6,
ge=0.0,
le=1.0,
description="Min relevance score (0-1) for hook/MCP recall; results below are dropped (console Search unaffected)",
description="[DEPRECATED] Retained as emergency rollback only. Use filter_score instead. "
"Original purpose: min relevance score for hook/MCP recall. "
"Replaced by filter_score after eval proved sigmoid scores unsuitable for hard filtering.",
)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟠 Major | 🏗️ Heavy lift

Make recall_min_score a working rollback setting.

Line 135 states that the router no longer reads this field. The supplied search_memories path injects only settings.filter_score for strict recall. Changing recall_min_score therefore cannot restore the legacy behavior promised here.

Add an explicit rollback path that consumes recall_min_score, or remove the setting and its rollback documentation. Add an integration test that proves a configured rollback value changes filtering.

🤖 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/hebb/config/settings.py` around lines 133 - 145, The deprecated
recall_min_score setting is documented as a rollback switch but is not consumed
by the search path. Add an explicit rollback branch in the supplied
search_memories flow that uses configured recall_min_score for legacy filtering,
and add an integration test proving that changing the rollback value changes
results; otherwise remove the setting and its rollback documentation.

@coderabbitai

coderabbitai Bot commented Aug 2, 2026

Copy link
Copy Markdown

Tip

For best results, initiate chat on the files or code changes.

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

for file in \
  src/hebb/server/routers/search.py \
  src/hebb/retrieval/searcher.py \
  src/hebb/mcp/server.py \
  src/hebb/integrations/claude_code/recall.py \
  src/hebb/config/settings.py \
  src/hebb/models/memory.py \
  tests/integration/server/test_api.py \
  tests/unit/integrations/test_claude_code_hooks.py \
  tests/unit/test_audit_retrieval.py
do
  printf '\n===== OUTLINE: %s =====\n' "$file"
  ast-grep outline "$file" --items all 2>/dev/null || true
done

printf '\n===== SCORE-RELATED IMPLEMENTATION =====\n'
rg -n -C 6 'filter_score|recall_min_score|rerank_floor_ratio|min_score|strict_recall' \
  src/hebb/server/routers/search.py \
  src/hebb/retrieval/searcher.py \
  src/hebb/mcp/server.py \
  src/hebb/integrations/claude_code/recall.py \
  src/hebb/config/settings.py \
  src/hebb/models/memory.py

printf '\n===== SCORE-RELATED TESTS =====\n'
rg -n -C 5 'filter_score|recall_min_score|rerank_floor_ratio|min_score|strict_recall' \
  tests/integration/server/test_api.py \
  tests/unit/integrations/test_claude_code_hooks.py \
  tests/unit/test_audit_retrieval.py

Length of output: 73038


@ch-qiaoyongheng Thanks for the clarification. I verified the current implementation. The stated rollback sequence does not reach the legacy dual-scale branch.

For MCP, search_memory() always sends strict_recall=True. In src/hebb/server/routers/search.py, Lines 61-67 convert an explicit request min_score into filter_score when filter_score is absent. The searcher then uses composite filtering. It does not reach the legacy condition at src/hebb/retrieval/searcher.py, Lines 293-302.

For the Claude Code recall hook, _resolve_hook_filter_score() always returns the configured filter_score, including 0.0. _fetch_filtered() prefers a non-None filter_score over min_score. Therefore, the hook sends filter_score: 0.0 and omits min_score. This also cannot reach the legacy branch.

The legacy branch requires both conditions:

query.filter_score == 0.0 and query.min_score > 0.0

The current MCP and hook request paths prevent that combination. Therefore, recall_min_score is currently reference-only, and the documented emergency rollback path is not functional through the stated interfaces. I am retaining the finding.

🐇 🔎


✏️ Learnings added
Learnt from: ch-qiaoyongheng
URL: https://github.com/afx-team/hebb-mind/pull/63

Timestamp: 2026-08-02T14:00:16.188Z
Learning: In PR `#63`, `src/hebb/retrieval/searcher.py` uses legacy dual-scale filtering only when `MemoryQuery.filter_score == 0.0` and `MemoryQuery.min_score > 0.0`. However, `src/hebb/server/routers/search.py` converts an explicit `min_score` into `filter_score` for `strict_recall=True` requests, and `src/hebb/mcp/server.py` always enables `strict_recall`. In `src/hebb/integrations/claude_code/recall.py`, a resolved `filter_score` takes precedence over `min_score`. Thus the documented rollback sequence of `filter_score=0.0` plus explicit `min_score` cannot currently reach the legacy filtering branch through MCP or the Claude Code recall hook.

Note: Learnings are effective only in the context of similar code segments. To apply general coding guidelines, please configure review instructions. You can manage existing learnings in the app.

You are interacting with an AI system.

@ch-qiaoyongheng

Copy link
Copy Markdown
Author

@coderabbitai full review

@coderabbitai

coderabbitai Bot commented Aug 4, 2026

Copy link
Copy Markdown
✅ Action performed

Full review finished.

@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: 2

🤖 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 `@eval/reports/threshold_calibration/threshold_calibration_report_EN.md`:
- Line 127: Update the lenient approach entries for filter_score/min_score 0.5
in the threshold calibration report, including the line 127 description and the
lenient table column at line 133, from 93.0% to the source-aligned 93.5%. Keep
the other metrics and existing references unchanged.

In `@src/hebb/server/routers/search.py`:
- Around line 61-67: Update the strict-recall threshold handling so an
explicitly supplied min_score remains on the legacy filtering path instead of
being copied into updates["filter_score"]. Only inject settings.filter_score
when neither filter_score nor min_score appears in query.model_fields_set,
preserving the existing behavior for callers that provide filter_score.
🪄 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: b7cea9e4-9371-47cd-a6c6-63284682e4f2

📥 Commits

Reviewing files that changed from the base of the PR and between 36ce983 and f1cd11a.

📒 Files selected for processing (21)
  • CHANGELOG.md
  • eval/reports/threshold_calibration/full-A_composite_baseline.json
  • eval/reports/threshold_calibration/full-B_rerank_sigmoid_floor.json
  • eval/reports/threshold_calibration/full-C_rerank_composite_floor.json
  • eval/reports/threshold_calibration/threshold_calibration_report.md
  • eval/reports/threshold_calibration/threshold_calibration_report_EN.md
  • eval/retrieval_lab.py
  • eval/threshold_probe.py
  • src/hebb/config/settings.py
  • src/hebb/integrations/claude_code/recall.py
  • src/hebb/mcp/server.py
  • src/hebb/models/memory.py
  • src/hebb/retrieval/searcher.py
  • src/hebb/server/routers/search.py
  • src/hebb/static/js/components/activate.js
  • src/hebb/static/js/components/config-section.js
  • src/hebb/static/js/components/system.js
  • src/hebb/static/js/i18n.js
  • tests/integration/server/test_api.py
  • tests/unit/integrations/test_claude_code_hooks.py
  • tests/unit/test_audit_retrieval.py

| `filter_score` | — | **0.6** | R@10=91.7%, empty recall=1.0%, avg 7.4 results — balances filtering purity and recall |
| `rerank_floor_ratio` | 0.625 | **Retained but not used in default filtering** | Kept as a fallback switch; can be enabled when sigmoid filtering is needed |

**More lenient approach (if zero empty recall is required)**: `filter_score = 0.5`, R@10=93.0%, empty recall=0.0%, avg 8.7 results.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Align the lenient-approach R@10 with the source data.

Line 127 and the lenient column at line 133 state R@10 = 93.0% for filter_score = 0.5. full-C_rerank_composite_floor.json records R@10 0.9348 at min_score 0.50, and lines 89 and 209 of this document already print 93.5%. The document contradicts itself.

🐛 Proposed fix
-**More lenient approach (if zero empty recall is required)**: `filter_score = 0.5`, R@10=93.0%, empty recall=0.0%, avg 8.7 results.
+**More lenient approach (if near-zero empty recall is required)**: `filter_score = 0.5`, R@10=93.5%, empty recall=0.2%, avg 8.7 results.
-| R@10          | 53.5%                          | **91.7%**                    | 93.0%                    |
+| R@10          | 53.5%                          | **91.7%**                    | 93.5%                    |

Also applies to: 133-133

🤖 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 `@eval/reports/threshold_calibration/threshold_calibration_report_EN.md` at
line 127, Update the lenient approach entries for filter_score/min_score 0.5 in
the threshold calibration report, including the line 127 description and the
lenient table column at line 133, from 93.0% to the source-aligned 93.5%. Keep
the other metrics and existing references unchanged.

Comment on lines +61 to +67
if query.strict_recall and "filter_score" not in query.model_fields_set:
if "min_score" in query.model_fields_set:
# Backward compat: convert legacy min_score to filter_score
updates["filter_score"] = query.min_score
else:
# Default: use configured filter_score
updates["filter_score"] = settings.filter_score

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Keep an explicit min_score on the legacy filtering path.

When a strict-recall caller sends min_score, this code converts it to filter_score. The searcher then bypasses rerank_floor_ratio. This makes the documented emergency rollback path unavailable to strict-recall clients.

Only inject settings.filter_score when neither threshold was supplied.

Proposed fix
-    if query.strict_recall and "filter_score" not in query.model_fields_set:
-        if "min_score" in query.model_fields_set:
-            # Backward compat: convert legacy min_score to filter_score
-            updates["filter_score"] = query.min_score
-        else:
-            # Default: use configured filter_score
-            updates["filter_score"] = settings.filter_score
+    if (
+        query.strict_recall
+        and "filter_score" not in query.model_fields_set
+        and "min_score" not in query.model_fields_set
+    ):
+        updates["filter_score"] = settings.filter_score
🤖 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/hebb/server/routers/search.py` around lines 61 - 67, Update the
strict-recall threshold handling so an explicitly supplied min_score remains on
the legacy filtering path instead of being copied into updates["filter_score"].
Only inject settings.filter_score when neither filter_score nor min_score
appears in query.model_fields_set, preserving the existing behavior for callers
that provide filter_score.

@ch-qiaoyongheng

Copy link
Copy Markdown
Author

@coderabbitai
We intentionally do not preserve the legacy rollback path. The design decision is:

▎ - recall_min_score and rerank_floor_ratio are deprecated and no code path reads them. They are retained only for backward compatibility with existing hebb.json files.
▎ - filter_score is the sole production filtering mechanism.
▎ - The min_score → filter_score conversion in the router is correct behavior: it ensures all strict-recall clients use the composite-score path, which eval proved superior
▎ (R@10 91.7% vs 53.5%, empty recall 1.0% vs 39.0%).

▎ The "emergency rollback" documentation was inaccurate — we've already updated it to reflect this in settings.py. Dismissing this finding.

@coderabbitai

coderabbitai Bot commented Aug 4, 2026

Copy link
Copy Markdown

Tip

For best results, initiate chat on the files or code changes.

@ch-qiaoyongheng Acknowledged. The finding relied on an incorrect assumption that the deprecated settings must provide a usable rollback path.

The current behavior matches the intended design:

  • filter_score is the only production filtering mechanism.
  • The router conversion from min_score to filter_score is intentional.
  • recall_min_score and rerank_floor_ratio exist only for backward-compatible parsing of existing hebb.json files.
  • The updated settings.py documentation removes the inaccurate rollback claim.

I withdraw the finding.

🐇


✏️ Learnings added
Learnt from: ch-qiaoyongheng
URL: https://github.com/afx-team/hebb-mind/pull/63

Timestamp: 2026-08-04T09:57:57.193Z
Learning: In PR `#63`, `filter_score` is the sole production strict-recall filtering mechanism. `src/hebb/server/routers/search.py` intentionally converts strict-recall `min_score` inputs to composite-score `filter_score`. `recall_min_score` and `rerank_floor_ratio` are deprecated compatibility settings retained only so existing `hebb.json` files continue to parse; production code must not treat them as a rollback mechanism.

Note: Learnings are effective only in the context of similar code segments. To apply general coding guidelines, please configure review instructions. You can manage existing learnings in the app.

You are interacting with an AI system.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

feat(retrieval): make the strict-recall score floor configurable + recalibrate for the cross-encoder scale

1 participant