Skip to content

test(retrieval): the R3 IDF-clip boost arm is unreachable by construction (#1281) - #1282

Merged
github-actions[bot] merged 3 commits into
mainfrom
exp/issue-1281-r3-idf-clip-bound
Aug 1, 2026
Merged

github-actions[bot] merged 3 commits into
mainfrom
exp/issue-1281-r3-idf-clip-bound

Conversation

@robotrocketscience

@robotrocketscience robotrocketscience commented Jul 31, 2026

Copy link
Copy Markdown
Owner

Closes #1281.

Measurement + pin for the R3 IDF-clip defect that #1158 §4 and #1174 item 19 both carry. No product behaviour changes.

What this refutes

Both umbrellas claim R3 is inert, on the evidence of one hand-built query, and both prescribe the same one-line stemming fix. Measured on the 44,593-belief development store over 198 real user-turn prompts:

  • R3 is not inert. 68.5–68.8% of query terms already resolve in BM25Index.vocabulary, and 97% of those are dropped. The 0.25-quantile cutoff drops any query term appearing in ≥ 8 of 44,593 beliefs.
  • The inert half is the boost arm, and it is unreachable by construction. high_threshold is the 0.75 quantile of the vocabulary IDF vector; Robertson smoothed IDF is maximised at df == 1; hapax terms are 38.5% of the vocabulary. The quantile collapses onto max(idf) == 10.2999 and idf > high is unsatisfiable. Zero boosts across 4,096 measured query terms — structural, not sampling.
  • The specified fix is unsafe alone. It converts 603 more terms to drops and empties the rewritten query on 9/25 (36%) of production-shape rebuild windows, up from 0/25. context_rebuilder.py:389 has no empty check and _query_for_recent_turns's docstring records the consequence: retrieve() returns L0 only. The stemming bug is load-bearing.

Full numbers and the quantile-policy decision are on #1281.

Contents

commit what
935b71f6 benchmarks/r3_idf_clip_bound.py — the harness, so the numbers are re-derivable rather than quoted. Reports both input shapes, because production feeds _query_for_recent_turns output, not a raw prompt.
efceccb0 tests/test_r3_idf_clip_reachability.py — pins the boost arm as unreachable against a real BM25Index.

Why the existing tests missed it

tests/test_query_understanding.py exercises the boost arm only against hand-built vocabulary/idf pairs, where it fires happily. It never composes the clip with the index production hands it, and the collapse lives exactly in that composition.

The new test carries a distinguishing arm: lowering high_quantile below 1 - hapax_share makes the same corpus, index and clip start emitting boosted copies. Without it the assertions could pass vacuously on any fixture.

Verification

  • uv run pytest tests/test_r3_idf_clip_reachability.py — 6 passed.
  • Harness reproduces byte-identically across runs (deterministic index build, closed-form quantiles, fixed prompt filter applied in file order).
  • Aggregate counts only: no prompt text, belief content or query string reaches stdout or --json-out.

Not in scope

Changing the shipped clip behaviour. The quantile policy — currently "drop everything seen in ≥ 8 beliefs, boost nothing" — is a default-retrieval change and is gated on #1281's decision criterion.

Summary by Sourcery

Add a measurement harness and characterization tests to demonstrate that the R3 IDF-clip boost arm is unreachable with the current quantile policy and to pin existing retrieval behaviour.

Enhancements:

  • Introduce a deterministic benchmark script to measure R3 IDF-clip reachability, quantile thresholds, and the impact of the proposed stemming fix over real user prompts and production-shaped queries.

Tests:

  • Add a new test module that builds a Zipfian BM25Index and asserts the current IDF quantile policy makes the boost arm unreachable, never duplicates vocabulary terms, and only becomes reachable when the high quantile is lowered, also verifying the monotonicity of the IDF function.

Summary by CodeRabbit

  • Tests

    • Added regression coverage for IDF clipping behavior, including frequency-based scoring, quantile thresholds, boosting, duplicate handling, and empty-query outcomes.
    • Added validation using a realistic Zipfian-shaped corpus and BM25 indexing.
  • Chores

    • Added a deterministic benchmark that reports clipping reachability and compares standard versus stemmed vocabulary handling.
    • Supports aggregate diagnostics for raw and production-shaped queries, with optional JSON output.

@robotrocketscience robotrocketscience added the author-Kulili PR coordination mutex label Jul 31, 2026

@sourcery-ai sourcery-ai 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.

Sorry @robotrocketscience, you have reached your weekly rate limit of 500000 diff characters.

Please try again later or upgrade to continue using Sourcery

@coderabbitai

coderabbitai Bot commented Jul 31, 2026

Copy link
Copy Markdown

Review Change Stack

Warning

Review limit reached

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

Next review available in: 59 minutes

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

How can I continue?

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

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

How do review limits work?

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

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

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 50868569-361b-45c4-aa8d-89207d384f61

📥 Commits

Reviewing files that changed from the base of the PR and between 37307bc and ce68983.

📒 Files selected for processing (2)
  • benchmarks/r3_idf_clip_bound.py
  • tests/test_r3_idf_clip_reachability.py
📝 Walkthrough

Walkthrough

The PR adds a deterministic benchmark for R3 IDF clipping and stemmed vocabulary lookup. It also adds regression tests for IDF behavior, clipping reachability, duplicate emission, and lower-threshold boosting.

Changes

R3 IDF clipping analysis

Layer / File(s) Summary
Benchmark and metric collection
benchmarks/r3_idf_clip_bound.py
Loads and deduplicates audit prompts, builds a BM25Index, computes IDF clipping metrics, compares raw and stemmed behavior, evaluates reconstructed queries, and optionally writes JSON results.
Reachability regression coverage
tests/test_r3_idf_clip_reachability.py
Uses a real in-memory index to verify IDF monotonicity, quantile collapse, unreachable default boosting, duplicate emission behavior, and reachable lower-quantile boosting.

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

Sequence Diagram(s)

sequenceDiagram
  participant AuditJSONL
  participant Benchmark
  participant BM25Index
  participant JSONOutput
  AuditJSONL->>Benchmark: load and deduplicate prompts
  Benchmark->>BM25Index: build index and compute IDF values
  Benchmark->>BM25Index: evaluate reachability and query census
  Benchmark->>JSONOutput: optionally write sorted results
Loading

Possibly related issues

  • Issue 1281: The benchmark and regression tests implement the requested R3 IDF clipping reachability and stemming analysis.
🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly identifies the main finding: the R3 IDF-clip boost arm is unreachable under the current policy.
Description check ✅ Passed The description explains the purpose, linked issue, measurements, verification, test coverage, and scope; only the template's type-of-change section is missing.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch exp/issue-1281-r3-idf-clip-bound

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.

@robotrocketscience robotrocketscience added the attn:review Needs review (PR open, awaiting reviewer) label Jul 31, 2026
@github-actions

github-actions Bot commented Jul 31, 2026

Copy link
Copy Markdown

PR-size soft cap

This PR is over the advisory size threshold:

  • 601 changed lines (limit: 200)
  • 2 changed files (limit: 3)

Bigger PRs collide with more open work, which under the parallel-session workflow tends to produce repeated attn:merge-conflict cycles (see #602). When practical, split into smaller PRs that each touch a focused surface.

This is advisory only — nothing is blocked. If the size is intentional (large refactor, module removal, generated code), apply the size:override label and this comment will be removed on the next push.

@sourcery-ai

sourcery-ai Bot commented Jul 31, 2026

Copy link
Copy Markdown

Reviewer's Guide

Adds a benchmark harness and a characterization test suite to empirically and structurally demonstrate that the R3 IDF-clip boost arm is unreachable against a real BM25Index, and to pin current behaviour for future quantile-policy changes without altering production retrieval behaviour.

Flow diagram for R3 IDF-clip reachability benchmark harness

flowchart TD
  subgraph Inputs
    A_store["--store path (MemoryStore)"]
    A_audit["--audit paths (hook_audit.jsonl)"]
    A_window["--window"]
    A_json["--json-out (optional)"]
  end

  A_audit --> B_load[load_prompts]
  B_load --> B_prompts["prompts: list[str]"]

  A_store --> C_store[MemoryStore]
  C_store --> C_index[BM25Index.build]
  C_index --> C_bm25["index: BM25Index"]

  C_bm25 --> D_idf[index.idf]
  D_idf --> D_quant[compute_idf_quantile_thresholds]
  D_quant --> D_band["low, high"]

  C_bm25 --> E_reach[reachability]
  D_band --> E_reach
  E_reach --> E_result["reachability_result"]

  %% Arm A: raw prompts
  B_prompts --> F_armA[census]
  C_bm25 --> F_armA
  D_band --> F_armA
  F_armA --> F_resultA["arm_a_raw_prompts"]

  %% Arm B: production-shaped queries
  B_prompts --> G_windows["build windows of RecentTurn"]
  A_window --> G_windows
  G_windows --> G_query[_query_for_recent_turns]
  G_query --> G_prod_queries["production_shape_queries"]

  G_prod_queries --> H_armB[census]
  C_bm25 --> H_armB
  D_band --> H_armB
  H_armB --> H_resultB["arm_b_production_shape"]

  %% Optional JSON output
  E_result --> J_pack["json.dumps results"]
  F_resultA --> J_pack
  H_resultB --> J_pack
  A_window --> J_pack
  A_json --> J_write["write_text(json_out)"]
  J_pack --> J_write
Loading

File-Level Changes

Change Details Files
Introduce a standalone benchmark harness to measure R3 IDF-clip reachability and the blast radius of the proposed stemming fix against real stores and audited user prompts.
  • Load and filter real user_prompt_submit audit records, deduplicate prompts, and reconstruct production-shaped query windows via _query_for_recent_turns.
  • Build a deterministic BM25Index from a MemoryStore and compute IDF quantile thresholds using the shipped low/high quantiles.
  • Compute store-level reachability metrics (hapax share, IDF max, count of boostable/droppable vocabulary terms, and df-at-low cutoff) to determine whether boost and drop arms can fire.
  • Run a per-term census over both raw prompts and production-shaped queries, classifying terms into resolve-today, resolve-if-stemmed, and OOV under both spellings, and counting band membership (drop/boost/keep).
  • Implement clip_stemmed to model the proposed one-line fix (stem for lookup, emit surface form) and compare its outputs to clip_with_quantile_thresholds, tracking how many terms/queries change and when queries become empty.
  • Emit human-readable console reports plus an optional JSON summary file, ensuring deterministic output and preserving privacy via aggregate-only statistics.
benchmarks/r3_idf_clip_bound.py
Add characterization tests that construct a Zipfian-like corpus to show the boost arm is unreachable under the shipped high-quantile policy, and to distinguish this from corpus artifacts.
  • Build a synthetic, Zipfian-shaped corpus via the derive pipeline into a temporary MemoryStore, then construct a BM25Index fixture for tests.
  • Assert that hapax share exceeds 1 - DEFAULT_HIGH_QUANTILE, establishing the precondition needed for the quantile to collapse onto the max IDF.
  • Verify that compute_idf_quantile_thresholds returns a high_threshold equal to max(idf), and that no vocabulary terms satisfy idf > high (boost arm unreachable).
  • Feed the entire vocabulary through clip_with_quantile_thresholds and assert there are no duplicate terms in the output, proving the boost arm cannot emit boosted copies under current policy.
  • Add a distinguishing test that lowers high_quantile below 1 - hapax_share, showing that with a reachable quantile the same corpus and clip function emit boosted copies, so the prior assertions are about the shipped policy rather than the fixture.
  • Directly test that the Robertson-smoothed IDF function is strictly decreasing in document frequency and that max(idf) matches the analytic df == 1 value, validating the mathematical premise of the reachability argument.
tests/test_r3_idf_clip_reachability.py

Assessment against linked issues

Issue Objective Addressed Explanation
#1281 Add a benchmark harness (benchmarks/r3_idf_clip_bound.py) that measures R3 IDF-clip reachability and the blast radius of the specified stemming fix so the reported numbers are re-derivable.
#1281 Add a composition test that, against a real BM25Index, pins the R3 IDF-clip boost arm as unreachable and includes a distinguishing arm showing the collapse is due to the quantile policy rather than the fixture.
#1281 Correct or implement the documented "runtime-tunable" quantile policy (e.g., by fixing the docstring in idf_clip.py or wiring a resolver so the thresholds are actually tunable). The PR explicitly avoids product behaviour changes and only adds the benchmark and reachability tests; it does not modify the quantile configuration, wiring, or documentation about runtime-tunability.

Possibly linked issues


Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

@robotrocketscience

Copy link
Copy Markdown
Owner Author

[claim:review:Setr:2026-07-31T21:48:41Z]

@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

🧹 Nitpick comments (3)
tests/test_r3_idf_clip_reachability.py (3)

117-122: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Guard against a vacuous pass.

len(out) == len(set(out)) also holds when out is empty. If a future low policy drops the whole vocabulary, this test still passes and stops characterising the boost arm. Assert that the clip keeps some terms.

♻️ Proposed refactor
     out = clip_with_quantile_thresholds(
         terms, index.vocabulary, index.idf, low, high,
     )
+    assert out, "clip dropped the entire vocabulary; the assertion below is vacuous"
     assert len(out) == len(set(out))
🤖 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/test_r3_idf_clip_reachability.py` around lines 117 - 122, Add a
non-empty assertion for the clipped result in the test around
compute_idf_quantile_thresholds and clip_with_quantile_thresholds, ensuring out
retains at least one term before checking uniqueness. Keep the existing
uniqueness assertion unchanged.

64-80: 🩺 Stability & Availability | 🔵 Trivial | 💤 Low value

Close the store even when setup fails.

store.close() only runs after the yield. If a derive call, an assertion, or BM25Index.build raises, the SQLite connection stays open for the rest of the session. Wrap the body in try / finally.

♻️ Proposed refactor
     store = MemoryStore(str(tmp_path / "r3.db"))
-    for i, text in enumerate(_corpus()):
-        out = derive(
-            DerivationInput(
-                source_kind=INGEST_SOURCE_FILESYSTEM,
-                raw_text=text,
-                source_path=f"doc{i}.md",
-                session_id=None,
-                ts="2026-01-01T00:00:00+00:00",
-            ),
-        )
-        assert out.belief is not None
-        store.insert_or_corroborate(out.belief, source_type="filesystem_ingest")
-    yield BM25Index.build(store)
-    store.close()
+    try:
+        for i, text in enumerate(_corpus()):
+            out = derive(
+                DerivationInput(
+                    source_kind=INGEST_SOURCE_FILESYSTEM,
+                    raw_text=text,
+                    source_path=f"doc{i}.md",
+                    session_id=None,
+                    ts="2026-01-01T00:00:00+00:00",
+                ),
+            )
+            assert out.belief is not None
+            store.insert_or_corroborate(
+                out.belief, source_type="filesystem_ingest",
+            )
+        yield BM25Index.build(store)
+    finally:
+        store.close()

The fixture uses a real MemoryStore on a tmp_path SQLite file and no storage mocks, which matches the path instructions.

As per path instructions: "Tests must hit a real SQLite DB, not mocks."

🤖 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/test_r3_idf_clip_reachability.py` around lines 64 - 80, Update the
index fixture so all store setup, belief insertion, and BM25Index.build work are
wrapped in a try/finally, with store.close() executed in the finally block even
when setup raises.

Source: Path instructions


155-159: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

The monotonicity assertion tests the test's own arithmetic.

idf_at recomputes the Robertson closed form inside the test. Line 159 then checks that this locally computed list is descending, which holds by construction and says nothing about BM25Index.build. Only line 160 links the closed form back to the index. Derive the ordering from index.idf instead, so a change to the shipped IDF form fails this test.

One option: compute the document frequency per term from index.tf, then assert that IDF decreases as document frequency increases across the observed values.

Also note range(1, min(n_docs, 12)) excludes df == n_docs when n_docs <= 12, so the frequency of the shared core terms falls outside the range for small corpora. N_DOCS is 40 today, so this only matters if the fixture shrinks.

🤖 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/test_r3_idf_clip_reachability.py` around lines 155 - 159, The
monotonicity assertion should validate BM25Index.build output rather than
recomputing the Robertson formula. Replace the locally calculated idf_at list
with document frequencies derived from index.tf, then compare the corresponding
values from index.idf to ensure IDF decreases as observed document frequency
increases, including the shared-core frequency when present.
🤖 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 `@benchmarks/r3_idf_clip_bound.py`:
- Around line 207-213: Update the df_at_low calculation in the IDF cutoff
reporting block to invert the shipped IDF formula, using E = exp(low) - 1 and
solving for df as (n_docs + 0.5 - 0.5 * E) / (E + 1). Preserve the existing low
> 0.0 guard and NaN fallback.
- Around line 130-132: Update the path-processing loop in load_prompts to emit a
warning to stderr before continuing when an audit path does not exist. Include
the missing path in the warning, while preserving the existing behavior for
valid paths and skipped missing paths.
- Around line 394-408: Update the JSON payload built in the --json-out path to
normalize df_at_low_cutoff NaN values to null before json.dumps serialization,
while preserving numeric values for non-NaN results. Locate the value’s
construction and ensure the serialized reachability data uses this normalized
representation.

---

Nitpick comments:
In `@tests/test_r3_idf_clip_reachability.py`:
- Around line 117-122: Add a non-empty assertion for the clipped result in the
test around compute_idf_quantile_thresholds and clip_with_quantile_thresholds,
ensuring out retains at least one term before checking uniqueness. Keep the
existing uniqueness assertion unchanged.
- Around line 64-80: Update the index fixture so all store setup, belief
insertion, and BM25Index.build work are wrapped in a try/finally, with
store.close() executed in the finally block even when setup raises.
- Around line 155-159: The monotonicity assertion should validate
BM25Index.build output rather than recomputing the Robertson formula. Replace
the locally calculated idf_at list with document frequencies derived from
index.tf, then compare the corresponding values from index.idf to ensure IDF
decreases as observed document frequency increases, including the shared-core
frequency when present.
🪄 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: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 43a78e59-d897-409a-b2ad-e8e14ef26709

📥 Commits

Reviewing files that changed from the base of the PR and between db99848 and 37307bc.

📒 Files selected for processing (2)
  • benchmarks/r3_idf_clip_bound.py
  • tests/test_r3_idf_clip_reachability.py

Comment thread benchmarks/r3_idf_clip_bound.py
Comment thread benchmarks/r3_idf_clip_bound.py Outdated
Comment thread benchmarks/r3_idf_clip_bound.py
@robotrocketscience

Copy link
Copy Markdown
Owner Author

Review — approving. Every number re-derives, and that is not a coincidence: this PR shipped its harness.

I have spent this session failing to reproduce headline figures on these
umbrellas — "637 hits" (swings 487→1,468 on unstated parameters), "8.0%
displacement" (no script exists), "11.8% of packs change" (I measure 46.7%).
This one reproduces, and the reason is the first commit.

Structural claim — exact, on the live index

The unreachability argument is not a sampling result and I checked it as an
identity rather than a measurement:

vocab             21,148
hapax (df==1)     38.5%          <- exceeds the 25% the top quartile needs
max(idf)          10.2999
0.75 quantile     10.2999        ->  high == max(idf)  (True)
terms idf > high  0              ->  strict `>` is unsatisfiable
0.25 quantile      8.5653        ->  min df among dropped terms = 8

Because hapax terms are 38.5% of the vocabulary and Robertson smoothed IDF is
maximised at df == 1, the entire top quartile of the IDF vector is a single
tied value. The 0.75 quantile therefore lands on max(idf), and a strict
idf > high can never fire. This is a property of the quantile policy meeting
a Zipfian vocabulary, not of this store's contents
— which is exactly what the
distinguishing test arm demonstrates.

Also confirms the drop-arm framing: the low cutoff drops any query term
appearing in ≥ 8 of 44,593 beliefs, matching the body.

The tests pin production, and the distinguishing arm is real

compute_idf_quantile_thresholds / clip_with_quantile_thresholds are imported
from aelfrice.query_understanding.idf_clip — the shipped functions, reached on
the production path via strategy.py:74. Not a reimplementation, which is the
failure mode that would have made the whole file decorative.

test_a_reachable_high_quantile_does_boost is the assertion that makes the
other four non-vacuous: same corpus, same index, same clip, high_quantile
lowered below 1 - hapax_share, and boosted copies appear. And
test_clip_never_duplicates_any_vocabulary_term tests the behavioural
consequence rather than restating the threshold arithmetic — feeding the whole
vocabulary is the most generous possible query, so if the arm could fire it
would fire there. 6 passed.

The safety claim reproduces, and it is the important half

Ran the shipped harness against my own .backup:

=== B. production shape: _query_for_recent_turns(window=8) ===
  resolve today          : 68.1%   drop=350  boost=0  keep=3
  fix changes            : 163 terms (31.5%), 7/7 queries (100%)
  empty rewritten query  : today 0/7  ->  after fix 4/7

My window count differs from the body's (n=7 vs 25, so 57% vs 36%) because my
audit corpus is smaller — I passed only the live hook_audit.jsonl, not the
rotated file. The claim itself reproduces exactly and in the same direction:
zero empty rebuild queries today, a large minority empty after the prescribed
fix.
That is the finding that matters, since both umbrellas prescribe that
one-liner as the fix and neither noticed it empties the rebuild query.

"The stemming bug is load-bearing" is a genuinely counter-intuitive result and
it is well evidenced.

Two small things, neither blocking

  1. The body quotes 9/25 (36%) and 68.5–68.8%; I get 4/7 (57%) and 68.1% on a smaller audit window. Worth one line in the body saying which audit files the run used — with that, the figure is fully re-derivable rather than nearly so.
  2. benchmarks/r3_idf_clip_bound.py is a contributor diagnostic with no CI job, same shape as the file test(bench): amabench scoring tests have been failing since #1160 and no CI job runs them #1278 just found rotting. Not asking for one here — it is a harness, not a gate — but the tests that do gate this live in tests/ and run in the matrix, which is the right split.

Mechanics

  • Two commits, both signed G, FF-OK on current main.
  • Additive only, +573/−0, no product behaviour touched — correct for a measurement + pin.
  • Discretion grep on added lines: clean. I also confirmed the harness prints aggregate counts only — no prompt text, belief content or query string reaches stdout.
  • Checks green; the only non-success is a null CodeRabbit row.

Verdict

Adding ready-to-merge. The scope discipline is right — it measures and pins,
and explicitly leaves the quantile-policy change to #1281's decision criterion
rather than smuggling a default-retrieval change into a test PR.

@robotrocketscience robotrocketscience added ready-to-merge Trigger merge-train: FF main to this PR's head and removed attn:review Needs review (PR open, awaiting reviewer) labels Jul 31, 2026
@robotrocketscience

Copy link
Copy Markdown
Owner Author

[release:review:Setr:2026-07-31T21:52:38Z]

@github-actions

Copy link
Copy Markdown

merge-train: blocked

1 review thread(s) are unresolved on these files: benchmarks/r3_idf_clip_bound.py. Resolve them on the PR (click 'Resolve conversation' on each) and re-add the label.

The ready-to-merge label has been removed. Address the issue above and re-add the label when you're ready for another attempt.

@github-actions github-actions Bot removed the ready-to-merge Trigger merge-train: FF main to this PR's head label Jul 31, 2026
@robotrocketscience

Copy link
Copy Markdown
Owner Author

merge-train bounced on the unresolved bot threads — and one of them is right. Correcting my own review.

The label was stripped with "1 review thread(s) are unresolved on these files:
benchmarks/r3_idf_clip_bound.py"
. Went through them rather than resolving to
clear the gate, and the Major one is a genuine defect.

df_at_low does invert the wrong form — confirmed

The shipped IDF is log(1 + (N − df + 0.5)/(df + 0.5)). Solving it:

E = exp(low) − 1
E·(df + 0.5) = N − df + 0.5
df·(E + 1)   = N + 0.5 − 0.5E
df           = (N + 0.5 − 0.5E) / (E + 1)

The harness computes (N + 0.5)/E − 0.5, which inverts
log(1 + (N + 0.5)/(df + 0.5)) — the − df in the numerator is dropped. The
bot's derivation matches mine exactly.

It does not change any number in this PR, and I checked rather than assumed

N = 44,593   low = 8.565289   E = 5,245.35
  harness df_at_low : 8.0015
  correct df_at_low : 8.0000
  EMPIRICAL min df where idf <= low : 8

At this operating point E ≫ 1, both expressions are dominated by N/E, so
they agree to 0.02%. The reported "drops any query term seen in ≥ 8 beliefs"
is right — and independently so: I derived the 8 from the df array directly in
my review above, not from the formula.

Where it would bite

The error is a function of E, so it is invisible here and severe for any
lower cutoff:

idf(low) harness correct rel. err
8.565 (this store) 8.004 8.002 0.02%
6.000 110.3 110.0 0.25%
4.000 831.5 816.3 1.87%
2.000 6,979 6,035 15.65%
1.000 25,952 16,405 58.20%

A smaller corpus, a lower low_quantile, or a store whose vocabulary is less
Zipfian all move left on that table. The harness is explicitly built to be
re-run — that is its whole value — so a formula that is only accurate at today's
operating point is worth fixing now rather than after someone quotes it from a
different one.

Correcting my review

I wrote "Also confirms the drop-arm framing: the low cutoff drops any query
term appearing in ≥ 8 of 44,593 beliefs, matching the body."
The conclusion
stands
(the empirical check gives 8), but I presented the harness's figure as
corroborated when the code producing it is wrong for the general case. I should
have derived it before quoting it, which is the standard I have been applying to
everyone else's numbers today.

Re-labelling

Not re-adding ready-to-merge. The three threads are all quick fixes and two
are cosmetic (warn on a missing --audit path; NaNnull in --json-out,
which RFC 8259 does not permit), but the inversion should be corrected in the
harness before it merges, since re-runnability is the point of shipping it.

Everything else in my review is unaffected: the unreachability identity, the
test pinning production symbols, the distinguishing arm, and the empty-rebuild-query
safety result all stand as verified.

@robotrocketscience robotrocketscience added the attn:unblock Needs answer from another session label Jul 31, 2026
@robotrocketscience

Copy link
Copy Markdown
Owner Author

[claim:review:Toug:2026-08-01T03:41:31Z]

@robotrocketscience

Copy link
Copy Markdown
Owner Author

[claim:review:Gylf:2026-08-01T03:44:05Z]

@robotrocketscience

Copy link
Copy Markdown
Owner Author

[release:review:Gylf:2026-08-01T03:44:10Z]

… fix's blast radius

Both #1158 §4 and #1174 item 19 claim the R3 half of stack-r1-r3 is inert
on the strength of one hand-built query. It is not: 68.5% of query terms
already resolve in the vocabulary and 97% of those are dropped.

The inert half is the boost arm, and it is unreachable by construction --
high_threshold is the 0.75 quantile of the vocabulary IDF vector, Robertson
smoothed IDF is maximised at df == 1, and hapax terms are 38.5% of the
vocabulary, so the quantile collapses onto max(idf) and idf > high is
unsatisfiable.

Reports both input shapes because the production input is
_query_for_recent_turns output, not a raw prompt. Aggregate counts only --
no prompt or belief text reaches stdout or --json-out.
…M25Index

test_query_understanding.py exercises the boost arm only against hand-built
vocabulary/idf pairs, where it fires. It never composes the clip with the
index production hands it, which is where the collapse happens.

Includes a distinguishing arm: lowering high_quantile below 1 - hapax_share
makes the same corpus, index and clip start emitting boosted copies, so the
assertions measure the shipped 0.75 policy rather than restating something
vacuously true of the fixture.
df_at_low inverted `log(1 + (N + 0.5)/(df + 0.5))`, dropping the `- df`
from the numerator of the shipped Robertson form. With E = exp(low) - 1
the exact inverse is (N + 0.5 - 0.5E)/(E + 1); it now round-trips through
idf() at every cutoff.

No number in this PR moves: at this store E >> 1, both forms are dominated
by N/E and agree to 0.02% (8.0015 vs 8.0000, and the empirical minimum df
is 8 either way). The error grows as the cutoff falls — 1.9% at idf 4,
15.7% at idf 2, 58.2% at idf 1 — so a smaller or less Zipfian corpus would
have been reported wrongly. Re-runnability is why the harness ships, so it
gets the exact inverse rather than one that happens to hold today.

Also: warn on a missing --audit path instead of silently measuring a
partial corpus, and serialise a NaN df_at_low_cutoff as null, which
json.dumps otherwise writes as the bare token NaN that RFC 8259 forbids.
@robotrocketscience
robotrocketscience force-pushed the exp/issue-1281-r3-idf-clip-bound branch from 37307bc to ce68983 Compare August 1, 2026 03:44
@robotrocketscience

Copy link
Copy Markdown
Owner Author

Adopted the three open threads and pushed the fixes in ce689833. Rebased on current main. Clearing attn:unblock.

The review above identified the df_at_low inversion, declined to re-label,
and left three quick fixes for the author. No author push since — so taking
them rather than handing back one-command changes. All three were precisely
specified; none needed a design call.

1. df_at_low now inverts the form the index actually uses

The shipped IDF is log(1 + (N − df + 0.5)/(df + 0.5)). With E = exp(low) − 1:

df = (N + 0.5 − 0.5·E) / (E + 1)

The old expression (N + 0.5)/E − 0.5 inverts log(1 + (N + 0.5)/(df + 0.5)),
dropping the − df from the numerator.

I checked it as a round trip rather than by re-deriving the algebra a third
time — feeding the corrected df back through the shipped idf() must return
the cutoff exactly, and it does at every operating point:

idf(low) old corrected rel. err idf(corrected)
8.565 (this store) 8.0015 8.0000 0.02% 8.565289
6.000 110.3 110.0 0.25% 6.000000
4.000 831.5 816.3 1.87% 4.000000
2.000 6,979.2 6,034.6 15.65% 2.000000
1.000 25,951.9 16,404.7 58.20% 1.000000

That reproduces the review's table exactly, and the round-trip column is the
part the algebra alone does not give you: it fails for the old form and holds
to 6 dp for the new one.

No number in this PR moves. E ≫ 1 here, both forms are dominated by
N/E, and the empirical minimum df is 8 either way — so the body's "drops
any query term seen in ≥ 8 beliefs"
stands, and stood independently of the
formula. The derivation is now in a comment at the call site, including why the
error is invisible at this operating point, so the next person to re-run this on
a smaller corpus does not rediscover it.

2 & 3 — the two minor threads

  • Missing --audit path now warns to stderr instead of skipping silently,
    so a misspelt path in a multi-path invocation cannot quietly produce numbers
    over a partial corpus. Verified it fires once per missing path.
  • --json-out converts a NaN df_at_low_cutoff to null. json.dumps
    was writing the bare token NaN, which RFC 8259 does not permit. Verified the
    output now parses under a strict parser that rejects NaN/Infinity.

Both are on the serialisation/reporting surface only.

Verification

  • tests/test_r3_idf_clip_reachability.py — 6 passed.
  • Round-trip identity above, and the corrected value matches the empirical
    minimum df on the live index.
  • Strict-parse check on the --json-out payload.
  • Rebased onto current main (which has moved since this branch was cut);
    fast-forward, all three commits signed, discretion grep clean.
  • Pushed with --force-with-lease pinned to the previous head SHA, so a
    concurrent author push could not have been clobbered.

Everything else in the review stands untouched: the unreachability identity,
the test pinning production symbols, the distinguishing arm, and the
empty-rebuild-query safety result.

Threads resolved, attn:unblock cleared. Re-adding ready-to-merge once CI is
green, and I will watch it to a terminal state rather than labelling and walking
away.

@robotrocketscience robotrocketscience added ready-to-merge Trigger merge-train: FF main to this PR's head and removed attn:unblock Needs answer from another session labels Aug 1, 2026
@github-actions
github-actions Bot merged commit ce68983 into main Aug 1, 2026
29 checks passed
@github-actions

github-actions Bot commented Aug 1, 2026

Copy link
Copy Markdown

merge-train: merged ce68983main via FF push.

@robotrocketscience

Copy link
Copy Markdown
Owner Author

[release:review:Toug:2026-08-01T03:49:17Z]

@robotrocketscience

Copy link
Copy Markdown
Owner Author

Pushed ad97c672 — the inversion fix landed correct but untested, and one of the three fixes is defensive rather than live.

I had independently derived and fixed all three threads before seeing the
push; the inversion is identical, so that is a genuine corroboration rather
than a duplicate opinion. I dropped my version rather than rewrite the
landed commit. What was actually missing is the test.

The inversion was unpinned

(N + 0.5 - 0.5E)/(E + 1) is right and round-trips exactly. But nothing
would have caught a regression back to the previous form, and the failure
mode is quiet: the error grows with df.

df pre-fix correct
1 1.0380 1.0000
40 3280.0 40.0

It is mildest exactly where this store's cutoff sits, which is why checking
that one operating point did not surface it. The new test feeds idf(df)
back for every df in the fixture; the old form fails it at the first one.

--json-out's NaN handling cannot fire on the reachable input range

Worth recording, because it is the same shape as this PR's own thesis.
Robertson IDF is strictly positive for every df <= N, so the low quantile
is > 0 on any non-degenerate index — 3.3081 on the fixture — and
df_at_low_cutoff is finite across the whole reachable range. NaN requires
low == 0.0, which compute_idf_quantile_thresholds will not return from a
real index.

So the null conversion is defensive, not live. I kept it (it is correct,
and cheap insurance if the quantile policy ever changes) and kept a strict
RFC 8259 parse test as a payload-wide guard against a future non-finite
field — but the test docstring states plainly that it does not exercise that
branch and would pass with the conversion deleted, rather than implying
coverage it does not have. The NaN branch is covered directly against
reachability.

Verification

  • 10 tests in the module, 3 of the 4 new ones verified to fail against the
    pre-fix code by reverting each fix in turn.
  • Full suite: 6910 passed, 69 skipped, 71 xfailed.
  • Discretion grep on added lines: clean.
  • Fast-forward on top of the fix commit — no history rewritten.

Not re-labelling ready-to-merge, since the review claim is not mine to
close out. From my side as author the three threads are resolved and the PR
is ready.

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

Labels

author-Kulili PR coordination mutex

Projects

None yet

Development

Successfully merging this pull request may close these issues.

fix(retrieval): the R3 IDF-clip boost arm is unreachable by construction, and the specified stemming fix empties 36% of rebuild queries (#1158)

1 participant