Skip to content

perf(retrieval): skim every head alongside the section ranking; read 30 pages, not 40 (HAL-1566) - #71

Open
hallelx2 wants to merge 2 commits into
halleluyaholudele/hal-1708-engine-llmgate-guard-fixfrom
halleluyaholudele/hal-1566-judgewalk-skim-every-page-so-head-skim-and-section-ranking
Open

hallelx2 wants to merge 2 commits into
halleluyaholudele/hal-1708-engine-llmgate-guard-fixfrom
halleluyaholudele/hal-1566-judgewalk-skim-every-page-so-head-skim-and-section-ranking

Conversation

@hallelx2

@hallelx2 hallelx2 commented Sep 28, 2026 •

Copy link
Copy Markdown
Owner

Why

With the guard fixed, a query is four sequential levels at ~0.6 s each. The head skim waited on the section ranking only to learn which pages to skim.

What

  • JudgeNavigator.SkimAll: every page's head is scored in the same round as the section ranking. The full read is chosen from the same candidates by the same scores, and a test holds it equal to the sequential path. It's on only in the persisted-pages path (every page is already in memory) and for documents up to 400 pages.
  • RankLeaves sends multiple batches together, as rankPages already did.
  • Default full-read budget goes from 40 to 30 pages, measured below.
  • navbench: -skim-all; -pages defaults to the engine budget.

Evidence

40 FinanceBench questions, repeated runs (evaluation §3):

mode hit median $/q
sequential, 40 pages 36, 35, 36 2.5–3.1 s 0.00402
skim-all, 40 pages 35, 36, 35 1.7–1.8 s 0.00431
skim-all, 30 pages (shipped) 36, 36, 36 1.8–2.0 s 0.00388
skim-all, 20 pages 35, 36 1.7–1.9 s 0.00345

The same two borderline questions flip in every mode, so the accuracy differences are noise. The shipped default is faster and cheaper than the sequential original.

Not yet measured: /v1/query end to end over HTTP and the database.

Tests: ranking and skim are in flight together; same pages read as the sequential path at 5/10/40 budgets; fallback over the page cap; RankLeaves batches sent together. go test -race green locally. CI blocked (HAL-1712).

Stacked on #69.

Closes HAL-1566

Summary by Sourcery

Accelerate document navigation by overlapping ranking work and page-head skimming while lowering the default full-read budget.

New Features:

  • Add concurrent page-head skimming alongside section ranking for persisted-page navigation, with a bounded fallback for larger documents.
  • Expose the skim-all mode in navbench and make its page budget default to the engine setting.

Enhancements:

  • Reduce the default full-page read budget from 40 to 30 pages while preserving navigation accuracy.
  • Send leaf-ranking batches concurrently to reduce ranking latency.

Tests:

  • Add coverage for concurrent ranking and skimming, equivalent page selection, long-document fallback, and concurrent leaf batches.

Navigation ran four sequential levels: rank sections, skim the heads of
the chosen sections' pages, read the best in full, follow a
cross-reference. The skim waited on the ranking only because of which
pages it read. A page's head score does not depend on the ranking, so
under SkimAll every page's head is scored in the same round as the
ranking and one level goes away. What is read in full is unchanged:
the ranked sections' pages up to CoarsePages, ordered by the same
scores, which a test holds equal to the sequential skim.

It costs the heads of pages outside the chosen sections, so it is on
only where every page is already in memory (the persisted-pages path),
and only for documents up to SkimAllMaxPages (400). A longer document
falls back to the sequential skim.

RankLeaves sends its batches together when a tree has more leaves than
one request holds, as rankPages already did.

navbench takes -skim-all.
Full pages are about 60% of a query's tokens. Measured on 40
FinanceBench questions with the head skim running alongside the
section ranking, repeated:

  40 pages  hit 35/36/35  $0.00431/q
  30 pages  hit 36/36/36  $0.00388/q   median 1.8-2.0 s
  20 pages  hit 35/36     $0.00345/q

All three sit inside Jev's run-to-run noise. 30 keeps headroom for
answers spread over several pages, which FinanceBench barely tests
and a smaller read would hurt first. With it, the faster navigation
also costs less than the sequential one did ($0.00402).

navbench's -pages now defaults to the engine's own budget instead of
restating 40.
Copilot AI lite review requested due to automatic review settings September 28, 2026 03:26

Copilot AI 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.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

@coderabbitai

coderabbitai Bot commented Sep 28, 2026

Copy link
Copy Markdown

Important

Review skipped

Auto reviews are disabled on base/target branches other than the default branch.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Advanced

Run ID: eb3b988e-38a1-418d-890a-2a4cd4dc6945

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review

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.

@sourcery-ai

sourcery-ai Bot commented Sep 28, 2026 •

Copy link
Copy Markdown

Reviewer's Guide

This PR removes a sequential retrieval stage by concurrently ranking sections and skimming page heads for persisted-page documents up to 400 pages, reuses the same scores to choose full reads, batches leaf-ranking requests concurrently, lowers the default full-read budget to 30 pages, and adds navbench controls plus regression tests.

Sequence diagram for concurrent persisted-page navigation

sequenceDiagram
    participant Strategy as JudgeWalkStrategy
    participant Navigator as JudgeNavigator
    participant LeafRanker as RankLeaves
    participant HeadRanker as rankPages
    participant Reader as FullPageReader

    Strategy->>Navigator: Navigate(query, leaves)
    Navigator->>Navigator: allLeafPages(loadPages)
    par Section ranking
        Navigator->>LeafRanker: RankLeaves(query, leaves)
        LeafRanker->>LeafRanker: Judge.Judge(batch requests)
    and Page-head skim
        Navigator->>HeadRanker: rankPages(query, all pages, headChars)
    end
    Navigator->>Navigator: Select ranked-section candidates by head scores
    Navigator->>Reader: Read up to maxPages (default 30)
    Reader-->>Strategy: Navigation result
Loading

Flow diagram for SkimAll fallback and full-read selection

flowchart TD
    A["Persisted pages navigation"] --> B{"SkimAll enabled?"}
    B -- No --> C["Rank sections"]
    B -- Yes --> D{"Page count <= 400 and > full-read budget?"}
    D -- No --> C
    D -- Yes --> E["Load all leaf pages"]
    E --> F["Concurrently rank sections and skim every page head"]
    F --> G["Keep ranked-section candidates"]
    G --> H["Order candidates by head score"]
    H --> I["Read top 30 pages in full"]
    C --> J["Rank heads for selected pages"]
    J --> I
Loading

File-Level Changes

Change Details Files
Parallelize page-head skimming with section ranking and reuse those scores for full-page selection.
  • Load and deduplicate all leaf pages for the persisted-pages optimization path.
  • Run leaf ranking and all-page head ranking concurrently when the document is within the configured cap.
  • Select the full-read pages from ranked-section candidates using the precomputed head scores, while retaining sequential fallback for larger documents.
  • Enable the optimization for persisted pages and add configurable maximum-document bounds.
pkg/retrieval/judgewalk.go
Make leaf-ranking requests execute concurrently across batches.
  • Build all leaf request batches before dispatch.
  • Run batches concurrently with cancellation on the first error while aggregating usage, request counts, and scores.
pkg/retrieval/judgewalk.go
Reduce the default full-read budget and expose benchmarking controls for the new retrieval mode.
  • Change the engine default from 40 to 30 full pages.
  • Add navbench support for selecting engine-default page budgets and enabling all-page skimming.
pkg/retrieval/judgewalk.go
cmd/navbench/main.go
Add regression coverage for concurrency, selection equivalence, fallback behavior, and batched ranking.
  • Verify section ranking and head skimming are in flight together.
  • Compare pages, evidence, and selected sections with the sequential path across multiple budgets.
  • Verify long documents use sequential fallback and leaf batches are dispatched together.
pkg/retrieval/judgewalk_skim_test.go

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

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

Hey - I've found 1 issue

Prompt for AI Agents
Please address the comments from this code review:

## Individual Comments

### Comment 1
<location path="pkg/retrieval/judgewalk.go" line_range="495-503" />
<code_context>
+				rankErr, headErr error
+			)
+			wg.Add(2)
+			go func() {
+				defer wg.Done()
+				ranked, lu, lr, rankErr = n.RankLeaves(ctx, query, leaves)
+			}()
+			go func() {
+				defer wg.Done()
+				heads, hu, hr, headErr = n.rankPages(ctx, query, all, n.headChars(), true)
+			}()
+			wg.Wait()
+			if rankErr != nil {
+				return nil, fmt.Errorf("judgewalk: rank leaves: %w", rankErr)
</code_context>
<issue_to_address>
**issue (bug_risk):** When either the concurrent section-ranking or head-skimming operation fails, the other operation keeps running because each ranking method creates and owns a separate child context; `Navigate` only waits for both and never cancels the sibling. The remaining requests therefore continue consuming provider capacity and tokens after the navigation has already become unrecoverable.

**Triggers:** When one of the two concurrent ranking operations returns an error while the other still has in-flight requests.

**Suggested fix:** Create one cancellable child context in `Navigate` and pass it to both operations, or use an `errgroup.Group` so either error cancels the sibling immediately.
</issue_to_address>

Sourcery is free for open source - if you like our reviews please consider sharing them ✨

Comment on lines +495 to +503
go func() {
defer wg.Done()
ranked, lu, lr, rankErr = n.RankLeaves(ctx, query, leaves)
}()
go func() {
defer wg.Done()
heads, hu, hr, headErr = n.rankPages(ctx, query, all, n.headChars(), true)
}()
wg.Wait()

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

issue (bug_risk): When either the concurrent section-ranking or head-skimming operation fails, the other operation keeps running because each ranking method creates and owns a separate child context; Navigate only waits for both and never cancels the sibling. The remaining requests therefore continue consuming provider capacity and tokens after the navigation has already become unrecoverable.

Triggers: When one of the two concurrent ranking operations returns an error while the other still has in-flight requests.

Suggested fix: Create one cancellable child context in Navigate and pass it to both operations, or use an errgroup.Group so either error cancels the sibling immediately.

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.

2 participants