Skip to content

fix(retrieval): widen exclusion until the search is exhausted, and say when it isn't (#1205) - #1206

Merged
github-actions[bot] merged 2 commits into
mainfrom
fix/issue-1205-exclusion-starvation
Jul 30, 2026
Merged

github-actions[bot] merged 2 commits into
mainfrom
fix/issue-1205-exclusion-starvation

Conversation

@robotrocketscience

@robotrocketscience robotrocketscience commented Jul 30, 2026

Copy link
Copy Markdown
Owner

Closes #1205. A defect in my own #1191 fix, found while auditing this session's changes after #1202 turned up a different one in the same batch.

The defect

#1187 fixed the exclusion arm shrinking the pack, by widening the candidate fetch and retrying. I bounded that at three rounds. Three rounds made the bound the normal termination rather than a backstop:

retired / matching survivors available returned (before) after
150 / 300 150 50 50
200 / 300 100 0 50
250 / 300 50 0 50

At l1_limit=50 the rounds reach 200. A query whose 200 strongest matches are all retired returns nothing, while a hundred current, matching beliefs sit below the widest fetch attempted. The starvation #1187 set out to remove was moved out 4×, not removed.

The worse half

It truncated silently. Nothing distinguished "the store holds two survivors" from "I stopped after three rounds with a hundred unread." That is the silent-cap shape #1160 is open about — and I argued against exactly it on #1184, declining a heapq.nlargest suggestion partly because its magic cutoff dropped candidates with no counter and no log line, before shipping the same thing one PR later.

The code comment asserted the wrong thing too:

when the rounds are exhausted the arm returns what it has, which is still strictly more than the pre-#1187 behaviour

At 200/300 it returned 0, and pre-#1187 also returned 0. "At least as much" holds; "strictly more" does not. Corrected.

What ships

Widening continues until the search is genuinely exhausted (len(rows) < limit). The round cap stays, raised to 8, but is now a backstop: reaching it means something pathological, and reaching it traces to stderr naming the limit reached and the survivor count, so a short pack is never mistaken for an empty store.

Verification

  • The three ratios above, parametrized, asserting min(l1_limit, available) rather than zero. Reverting the cap to 3 fails the 200 and 250 cases.
  • The trace is driven against the helper with a stub fetch that always returns a full page of retired beliefs, so the cap has to bind — the thing under test is the signal, not the corpus. Dropping the trace fails it.
  • A control that a naturally exhausted search stays quiet, so the assertion above cannot pass for a helper that warns on every call.
  • aelf eval --json byte-identical to the pinned baseline (the ci: trigger eval-calibration on the whole package (#1160) #1195 gate binds src/aelfrice/**).
  • 42 tests green across the lane and retrieval smoke; discretion grep clean; 2 signed commits.

Recorded for the bench, not fixed here

The exclusion arm costs more queries than demote on almost any store containing retired beliefs at all: with 2 of 300 retired at l1_limit=50 it already widens once (48 survivors < 50 requested), measuring 3.4 ms against demote's 1.7 ms on the same store. That asymmetry is inherent to filter-then-refetch and should be known before arm latency is compared, since it is the same class of confound as the pack-size difference #1191 removed.

Pushing exclusion into SQL (WHERE id NOT IN (...)) would remove the refetch entirely and is the better end state, but it needs the superseded set before the candidate query rather than after — a different shape from what both arms share today. Left for its own issue if the bench picks exclusion; noted in #1205's out-of-scope section.

Summary by Sourcery

Prevent supersession exclusion from silently truncating retrieval results when retired beliefs dominate a query, and surface a diagnostic when the widening cap binds.

Bug Fixes:

  • Ensure the supersession exclusion arm continues widening until the search is genuinely exhausted instead of stopping early at the round cap while survivors remain.
  • Emit a stderr trace when supersession exclusion hits its widening cap so a short pack can be distinguished from an empty store.

Enhancements:

  • Raise the supersession refetch round cap to make it a pathological backstop rather than a normal termination condition.

Documentation:

  • Document the corrected behaviour and diagnostics of the supersession exclusion arm in the v4 changelog entry for supersession handling.

Tests:

  • Add parametrized tests that supersession exclusion fills the pack whenever survivors are available, even when most matching beliefs are retired.
  • Add tests that the supersession exclusion helper logs a message when the widening cap binds and remains quiet when the search naturally exhausts.

@robotrocketscience robotrocketscience added the author-Setr PR coordination mutex label Jul 30, 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 30, 2026

Copy link
Copy Markdown

Warning

Review limit reached

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

Next review available in: 36 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: 6562f7f7-0439-4611-9d50-725d7abda062

📥 Commits

Reviewing files that changed from the base of the PR and between e9f2b30 and a8e0f93.

📒 Files selected for processing (3)
  • CHANGELOG/v4.md
  • src/aelfrice/retrieval.py
  • tests/test_supersession_lane.py

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 30, 2026
@sourcery-ai

sourcery-ai Bot commented Jul 30, 2026

Copy link
Copy Markdown

Reviewer's Guide

Adjusts supersession exclusion retrieval so widening continues until search exhaustion instead of stopping after a fixed number of rounds, adds explicit stderr signalling when the widening cap binds, raises the cap, and adds tests plus changelog notes to pin the new behavior and guard against regressions.

File-Level Changes

Change Details Files
Make supersession exclusion widening stop only when the search is exhausted and raise the widening cap to act as a backstop, with explicit stderr tracing when the cap binds.
  • Increase SUPERSESSION_REFETCH_ROUNDS from 3 to 8 and update the surrounding comment to describe the new semantics and rationale.
  • Introduce an exhausted flag in _fetch_excluding_superseded to distinguish natural search exhaustion (len(rows) < limit) from hitting the widening round cap.
  • After the widening loop, if the search was not exhausted, emit a stderr message describing that the supersession exclusion stopped at the round cap, including the limit and survivor counts, while still returning the (possibly short) pack.
src/aelfrice/retrieval.py
Add focused tests that the exclusion arm fills the pack while survivors remain, that hitting the widening cap produces a signal, and that natural exhaustion stays silent.
  • Add a parametrized test asserting that the exclusion arm returns min(l1_limit, available_survivors) across several superseded/total configurations and that no superseded beliefs appear in the result.
  • Add a test that drives _fetch_excluding_superseded with a stub store where all returned beliefs are superseded so the widening cap must bind, then asserts both an empty result set and the presence of the expected stderr trace.
  • Add a control test where the search naturally exhausts with fewer rows than requested and assert that no cap-binding trace is written to stderr.
tests/test_supersession_lane.py
Document the supersession exclusion starvation defect and its fix in the changelog for the 4.x series.
  • Append a detailed bullet describing the previous empty-pack behavior when retired beliefs dominated matches, the lack of signalling, and the new behavior where widening continues until genuine exhaustion and cap binding is traced to stderr.
CHANGELOG/v4.md

Assessment against linked issues

Issue Objective Addressed Explanation
#1205 Modify the supersession exclusion arm to widen the candidate fetch until the search is genuinely exhausted (len(rows) < limit), with a higher refetch cap that acts only as a backstop, and emit a stderr trace when the cap binds; correct the misleading comment about returning "strictly more" than pre-#1187 behaviour.
#1205 Add tests around supersession-heavy scenarios (e.g. 200/300 and 250/300 retired at l1_limit=50) to assert the exclusion arm returns min(l1_limit, available) rather than an empty pack, and tests that the cap-binding path emits a trace while naturally exhausted searches remain quiet.
#1205 Record in the bench-facing documentation/changelog that the exclusion arm issues more queries than the demote arm, so latency differences between arms are not compared naively.

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:Kulili:2026-07-30T19:27:08Z]

@robotrocketscience

Copy link
Copy Markdown
Owner Author

Reviewed against the branch, not the description.

Verified:

  • The parametrized cases are real distinguishing asserts. Reverting SUPERSESSION_REFETCH_ROUNDS to 3 fails exactly [200] and [250] and nothing else — the claimed sensitivity holds.
  • 37 lane tests green; CI green.
  • The exhausted flag is correct on every exit. I went in expecting the if not rows: path to fall through and emit a spurious trace on any query that matches nothing, which would have fired constantly. It does not — that path is an early return [], frozenset(), so it never reaches the check. Confirmed by driving the helper with a fetch returning []: no output. Worth stating since the diff hunk reads like a break in passing.
  • The quiet-control genuinely constrains the trace assertion, and the trace test stubs fetch so the cap must bind rather than depending on corpus shape. That is the right way round.

On the substance: raising a cap from 3 to 8 would normally just move the cliff, and the reason it doesn't here is len(rows) < limit becoming the real termination — the cap stops being load-bearing at all. Making binding observable rather than silent is the part that matters, and the changelog is right that it is the same silent-cap shape #1160 exists to remove.

One thing to know, not a defect in this PR: the #1187 CHANGELOG entry is duplicated on main and this branch inherits both copies (grep -c "Superseded beliefs were never demoted" CHANGELOG/v4.md → 2). The shorter version (cfe03a1f) is an exact character-prefix of the longer (aa61d065), so it is an amended entry that got kept alongside its own earlier revision — the failure mode of resolving CHANGELOG conflicts insert-only, which is correct for added entries and wrong for amended ones.

Nothing for you to do here; do not add it to this PR's scope. I am fixing it separately so it does not reach the release notes twice.

Approving.

@robotrocketscience robotrocketscience added the ready-to-merge Trigger merge-train: FF main to this PR's head label Jul 30, 2026
@robotrocketscience

Copy link
Copy Markdown
Owner Author

[release:review:Kulili:2026-07-30T19:29:29Z]

@robotrocketscience

Copy link
Copy Markdown
Owner Author

[claim:review:Gylf:2026-07-30T19:34:00Z]

…y when it isn't

The three-round cap was the normal termination, not a backstop. At
l1_limit=50 the rounds reach 200, so a query whose 200 strongest matches
are all retired returned an empty pack while current ones sat below it:

  superseded/matching   available   returned (before)   after
  150/300               150         50                  50
  200/300               100          0                  50
  250/300                50          0                  50

That is the starvation #1187 set out to remove, moved out 4x rather than
removed — my bound, introduced while fixing the original.

Worse, it truncated silently. Nothing distinguished 'the store holds two
survivors' from 'I stopped after three rounds with a hundred unread',
which is the silent-cap failure #1160 is open about, and which I argued
against on #1184 before shipping it a PR later.

The cap is now high enough that binding means something pathological,
and binding traces to stderr naming the limit reached and how many
survivors were found. The comment's 'strictly more than the pre-#1187
behaviour' claim is corrected: at 200/300 both returned 0, so it was
'at least as much'.

Closes #1205.
@robotrocketscience
robotrocketscience force-pushed the fix/issue-1205-exclusion-starvation branch from 65a1ade to a8e0f93 Compare July 30, 2026 19:36
@github-actions github-actions Bot removed the ready-to-merge Trigger merge-train: FF main to this PR's head label Jul 30, 2026
@github-actions
github-actions Bot merged commit a8e0f93 into main Jul 30, 2026
25 checks passed
@github-actions

Copy link
Copy Markdown

merge-train: merged a8e0f93main via FF push.

@robotrocketscience

Copy link
Copy Markdown
Owner Author

[release:review:Gylf:2026-07-30T19:48:15Z]

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

Labels

attn:review Needs review (PR open, awaiting reviewer) author-Setr PR coordination mutex

Projects

None yet

Development

Successfully merging this pull request may close these issues.

fix(retrieval): the supersession exclusion arm silently returns an empty pack when retired beliefs dominate

1 participant