Skip to content

feat(retrieve_uplift): per-flag NDCG@k bench harness for v1.7 default-on flip (#154) - #425

Merged
robotrocketscience merged 2 commits into
mainfrom
feat/issue-154-retrieve-bench-harness
May 5, 2026
Merged

feat(retrieve_uplift): per-flag NDCG@k bench harness for v1.7 default-on flip (#154)#425
robotrocketscience merged 2 commits into
mainfrom
feat/issue-154-retrieve-bench-harness

Conversation

@yoshi280

@yoshi280 yoshi280 commented May 5, 2026

Copy link
Copy Markdown
Collaborator

Task 1 of #154's default-on flip workflow. Builds the per-flag retrieve() NDCG@k bench harness; the lab-side run + per-flag flip decisions + README update + v2.0 tag are tasks 2–5 (sequenced after this lands).

What ships

Per-flag NDCG@k harness — tests/retrieve_uplift_runner.py

Drives a labeled query corpus through retrieve() once with all v1.7 flags off (baseline) and once with each flag toggled on (others off). Computes mean NDCG@k per arm and reports per-flag uplift.

Five flags exercised:

The runner is also a CLI:

AELFRICE_CORPUS_ROOT=~/projects/aelfrice-lab/tests/corpus/v2_0 \
    python -m tests.retrieve_uplift_runner

Prints a per-flag NDCG table; exits 1 if any flag regresses.

Bench-gate test — tests/bench_gate/test_retrieve_uplift.py

@pytest.mark.bench_gated. Asserts no flag regresses NDCG@k against the baseline. Skip-on-no-corpus.

The test deliberately does NOT enforce a positive-uplift threshold — per-flag flip thresholds are operator decisions on the resulting evidence table.

Unit tests — tests/test_retrieve_uplift_runner.py

Seven tests cover the NDCG@k arithmetic (perfect ordering = 1.0, empty expected = 0, no overlap = 0, partial overlap in (0,1), position matters) plus the FlagUplift dataclass and an end-to-end one-row synthetic corpus. All run on public CI without AELFRICE_CORPUS_ROOT.

Schema registration

  • tests/test_corpus_schema.py registers retrieve_uplift as a graded module: {query, beliefs, edges, expected_top_k (ordered), k}.
  • tests/corpus/v2_0/README.md updated layout + per-line-shape table. Notes expected_top_k is ordered (top-relevant first) — distinct from BFS modules' set-shaped expected_hit_ids — because NDCG cares about position.

Out of scope (subsequent #154 tasks)

Test plan

  • uv run pytest tests/test_retrieve_uplift_runner.py -v — 7 passed.
  • uv run pytest --ignore=tests/bench_gate -q — 2456 passed (+10 new), 23 skipped.
  • uv run pytest tests/test_corpus_schema.py -q — 15 skipped (no corpus mounted on public CI; schema entries registered).
  • Lab-side: AELFRICE_CORPUS_ROOT=... uv run pytest tests/bench_gate/test_retrieve_uplift.py — runs once a populated corpus lands.

Summary by Sourcery

Introduce a per-flag NDCG@k benchmark harness and gate for retrieve() v1.7 flags using a graded corpus module.

New Features:

  • Add a retrieve_uplift harness that evaluates per-flag NDCG@k uplift for retrieve() over a graded corpus and exposes a CLI for ad-hoc runs.

Enhancements:

  • Register the retrieve_uplift graded corpus schema for retrieve() uplift benchmarking.

Documentation:

  • Document the new retrieve_uplift graded corpus module and its ordered expected_top_k field in the v2.0 corpus README.

Tests:

  • Add corpus-free unit tests for the NDCG@k metric, per-flag uplift aggregation, and harness coverage over registered flags.
  • Add a bench-gated test that runs against the retrieve_uplift corpus and fails if any v1.7 flag regresses mean NDCG@k.

Summary by CodeRabbit

  • New Features

    • Added retrieve_uplift benchmark testing framework for evaluating feature flag performance improvements.
    • New corpus module for retrieve_uplift regression testing with structured data schema.
  • Tests

    • Added comprehensive unit tests for performance metric calculations and validation.
    • Added bench-gated regression test to detect performance regressions.
  • Documentation

    • Updated corpus schema documentation with retrieve_uplift module field specifications.

tests/retrieve_uplift_runner.py is the per-flag uplift harness for
the v1.7 default-on flip. For each row in the lab-side
retrieve_uplift corpus, it:

1. Builds a transient MemoryStore from the row's beliefs + edges.
2. Calls retrieve(store, query, k=row["k"], ...) once with all
   flags off (baseline) and once with each v1.7 flag toggled on
   (others off).
3. Scores each result list with graded NDCG@k against
   row["expected_top_k"].
4. Reports mean NDCG_off, NDCG_on, and uplift per flag.

Five flags exercised:

- use_bm25f_anchors (#148) — wired
- use_signed_laplacian (#149) — placeholder; warning-only flag, will
  report uplift=0 until the lane lands in retrieve()
- use_heat_kernel (#150) — wired via heat_kernel_enabled
- use_posterior_ranking (#151) — wired via non-zero posterior_weight
- use_hrr_structural (#152) — placeholder; same as #149

The runner is also a CLI:
    AELFRICE_CORPUS_ROOT=... python -m tests.retrieve_uplift_runner
prints the per-flag NDCG table and exits 1 if any flag regresses.

Seven unit tests in tests/test_retrieve_uplift_runner.py cover the
NDCG@k arithmetic (perfect, empty, no-overlap, partial, position
matters), the FlagUplift dataclass, and the end-to-end harness over a
one-row synthetic corpus. All run on public CI without
AELFRICE_CORPUS_ROOT.
…154)

- tests/bench_gate/test_retrieve_uplift.py asserts no v1.7 flag
  regresses NDCG@k against the all-flags-off baseline. Skip-on-no-
  corpus per the directory-of-origin rule. The test deliberately
  does NOT enforce a positive-uplift threshold — that's an operator
  decision per flag, made on the resulting evidence table.

- tests/test_corpus_schema.py registers retrieve_uplift as a graded
  module with required fields {query, beliefs, edges,
  expected_top_k (ordered), k}.

- tests/corpus/v2_0/README.md documents the new module under both
  the layout tree and the per-line-shape table. Notes
  expected_top_k is ORDERED (top-relevant first) — distinct from
  the BFS modules' set-shaped expected_hit_ids — because NDCG cares
  about position.

Once lab corpus rows land under tests/corpus/v2_0/retrieve_uplift/,
the bench-gate runs from the lab side via:

    AELFRICE_CORPUS_ROOT=~/projects/aelfrice-lab/tests/corpus/v2_0 \\
        uv run pytest tests/bench_gate/test_retrieve_uplift.py

The per-flag uplift table is the evidence the operator uses to
decide which flags flip default-on and which stay default-off in
the v1.7 release.
@sourcery-ai

sourcery-ai Bot commented May 5, 2026

Copy link
Copy Markdown

Reviewer's Guide

Adds a per-flag NDCG@k benchmark harness for retrieve() to support the v1.7 default-on flip decision, wires it into the corpus schema and lab corpus layout, and introduces both unit tests and a bench-gated test that enforce no per-flag NDCG regression against an all-flags-off baseline.

File-Level Changes

Change Details Files
Introduce a reusable per-flag retrieve() NDCG@k uplift harness and CLI that runs against a graded corpus module.
  • Implement FLAG_KWARGS and BASELINE_KWARGS tables to describe how each v1.7 flag maps into retrieve() kwargs for on vs baseline calls.
  • Add ndcg_at_k metric implementation using graded relevance derived from an ordered expected_top_k list, including handling for empty ground truth.
  • Define a FlagUplift dataclass and run_per_flag_uplift driver that seeds a fresh MemoryStore per row/arm, runs retrieve() once with all flags off and once per flag on, and aggregates mean NDCG@k and uplift.
  • Provide a _load_corpus helper to read retrieve_uplift/*.jsonl rows and main() CLI that loads rows from AELFRICE_CORPUS_ROOT (or --corpus-root), prints a formatted per-flag NDCG table, and exits nonzero if any flag regresses.
tests/retrieve_uplift_runner.py
Add unit tests that validate the NDCG@k arithmetic, FlagUplift behavior, and synthetic end-to-end harness behavior without requiring a mounted corpus.
  • Test NDCG@k for perfect ordering, empty expected, disjoint result lists, partial overlap, and position sensitivity.
  • Test the FlagUplift.uplift property as mean_ndcg_on - mean_ndcg_off.
  • Add an end-to-end test that runs run_per_flag_uplift over a one-row synthetic corpus and asserts coverage of all flags and valid NDCG ranges.
tests/test_retrieve_uplift_runner.py
Introduce a bench-gated test that runs the uplift harness against the lab retrieve_uplift corpus and enforces a no-regression constraint per flag.
  • Load the retrieve_uplift module via the existing load_corpus_module helper when aelfrice_corpus_root is mounted; skip if the corpus is absent.
  • Run run_per_flag_uplift across all corpus rows and assert that no flag has negative uplift, emitting a detailed per-flag NDCG summary on failure.
  • Mark the test as bench_gated to keep it out of normal CI runs and align with the lab-only corpus mounting pattern.
tests/bench_gate/test_retrieve_uplift.py
Register the new retrieve_uplift graded corpus module and document its schema and directory layout.
  • Extend the corpus schema registry to add a retrieve_uplift graded module with fields {query, beliefs, edges, expected_top_k (ordered), k}.
  • Update the tests/corpus/v2_0/README.md to include the retrieve_uplift/ directory under v2_0 and to document the ordered expected_top_k field and module row shape in the per-module table.
tests/test_corpus_schema.py
tests/corpus/v2_0/README.md

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

@coderabbitai

coderabbitai Bot commented May 5, 2026

Copy link
Copy Markdown
📝 Walkthrough

Walkthrough

This PR adds a regression testing suite for the retrieve_uplift corpus module, including a CLI harness that evaluates per-flag NDCG@k uplift from the retrieve() function, unit tests for the harness components, a bench-gated regression test, and updates to the corpus schema and documentation.

Changes

Retrieve Uplift Regression Testing

Layer / File(s) Summary
Schema & Documentation
tests/test_corpus_schema.py, tests/corpus/v2_0/README.md
Adds retrieve_uplift module entry to MODULES schema with required fields: query, beliefs, edges, expected_top_k, k. README documents module layout and per-row graded label format.
Core Harness Implementation
tests/retrieve_uplift_runner.py
Defines five feature flags with retrieve() kwargs, implements ndcg_at_k() scorer using graded relevance from expected_top_k ordering, FlagUplift dataclass to hold per-flag mean NDCG off/on and computed uplift delta, and run_per_flag_uplift() which seeds a fresh temp SQLite-backed MemoryStore per corpus row, runs retrieve for baseline and per-flag configurations, scores results with NDCG@k, and aggregates mean metrics.
Unit Tests
tests/test_retrieve_uplift_runner.py
Tests ndcg_at_k() edge cases (perfect ranking, empty ground truth, no overlap, partial overlap, position sensitivity), validates FlagUplift.uplift computation, and verifies run_per_flag_uplift() covers all configured flags with valid NDCG values in [0, 1].
Bench-Gated Regression Test
tests/bench_gate/test_retrieve_uplift.py
Loads retrieve_uplift corpus via load_corpus_module, computes per-flag uplifts, and fails with formatted per-flag NDCG_off / NDCG_on / uplift details if any flag has negative uplift.

Sequence Diagram

sequenceDiagram
    participant Corpus as Corpus (JSONL)
    participant Harness as Per-Flag Harness
    participant Store as MemoryStore<br/>(Temp SQLite)
    participant Retrieve as retrieve()
    participant Scorer as NDCG@k Scorer
    participant Aggregator as Metrics<br/>Aggregator

    loop For each corpus row
        Corpus->>Harness: Load row (beliefs, edges, query, expected_top_k, k)
        Harness->>Store: Create fresh MemoryStore
        Harness->>Store: Seed beliefs & edges
        loop For baseline + each flag
            Note over Harness: Prepare retrieve() kwargs<br/>(baseline or flag-enabled)
            Harness->>Retrieve: retrieve(store, query, k, **kwargs)
            Retrieve->>Store: Query & rank
            Retrieve-->>Harness: result_ids (top-k list)
            Harness->>Scorer: Compute ndcg_at_k(result_ids, expected_top_k, k)
            Scorer-->>Harness: NDCG value ∈ [0, 1]
            Harness->>Aggregator: Accumulate score
        end
        Harness->>Store: Close MemoryStore
    end
    
    Aggregator->>Aggregator: mean_ndcg_off per flag
    Aggregator->>Aggregator: mean_ndcg_on per flag
    Aggregator->>Aggregator: uplift = mean_ndcg_on − mean_ndcg_off
    Aggregator-->>Harness: FlagUplift list (one per flag)
    Harness->>Harness: Fail if any uplift < 0
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

  • robotrocketscience/aelfrice#311: Main PR extends the v2.0 corpus scaffold by adding the retrieve_uplift module, README entry, and schema/tests that directly modify the same MODULES/schema and corpus README introduced in this PR.
  • robotrocketscience/aelfrice#320: Adds a bench-gated retrieve_uplift test and runner that directly rely on the bench-gate harness patterns (aelfrice_corpus_root fixture and load_corpus_module) and extend the same corpus/module schema.
🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately summarizes the main change: adding a per-flag NDCG@k benchmarking harness for retrieve() v1.7 flags within the #154 workflow.
Description check ✅ Passed The description comprehensively covers the PR's objectives, deliverables, scope boundaries, test plan, and implementation details across multiple components.
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.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ 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 feat/issue-154-retrieve-bench-harness

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 and usage tips.

@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 left some high level feedback:

  • The --corpus-root default currently evaluates to Path('') (i.e. .) when AELFRICE_CORPUS_ROOT is unset, so args.corpus_root is never None and the "not set" branch is effectively dead; if you want missing env to be an error, consider computing the default explicitly (e.g. env = os.environ.get(...); default=None if not env else Path(env)).
  • The module docstring and FLAG_KWARGS comments say use_signed_laplacian and use_hrr_structural are "warning-only" placeholders, but there’s no actual warning or differentiation in run_per_flag_uplift; either wire in a warning/log when these flags are exercised or adjust the comments to match the current behavior.
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- The `--corpus-root` default currently evaluates to `Path('')` (i.e. `.`) when `AELFRICE_CORPUS_ROOT` is unset, so `args.corpus_root` is never `None` and the `"not set"` branch is effectively dead; if you want missing env to be an error, consider computing the default explicitly (e.g. `env = os.environ.get(...); default=None if not env else Path(env)`).
- The module docstring and `FLAG_KWARGS` comments say `use_signed_laplacian` and `use_hrr_structural` are "warning-only" placeholders, but there’s no actual warning or differentiation in `run_per_flag_uplift`; either wire in a warning/log when these flags are exercised or adjust the comments to match the current behavior.

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

@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

🧹 Nitpick comments (1)
tests/retrieve_uplift_runner.py (1)

214-232: ⚡ Quick win

Baseline NDCG is recomputed once per flag per row — O(2 × flags × rows) SQLite calls instead of O(flags + 1) × rows.

For each flag iteration, _row_ndcg(row, k, {}, tmp_root) creates a fresh SQLite DB, seeds it, and runs retrieve() — identically to every other flag's baseline call for the same row. With 5 flags and the ≥50-row v0.1 corpus target that's 500 calls instead of 300. The two placeholder flags (use_signed_laplacian, use_hrr_structural) make this especially wasteful since their "on" calls are also identical to baseline.

♻️ Proposed refactor — compute baseline once per row
-    for flag, kwargs_fn in FLAG_KWARGS.items():
-        kwargs_on = kwargs_fn()
-        off_total = 0.0
-        on_total = 0.0
-        for row in rows:
-            k = _default_k(row)
-            off_total += _row_ndcg(row, k, {}, tmp_root)
-            on_total += _row_ndcg(row, k, kwargs_on, tmp_root)
-        n = len(rows)
-        out.append(FlagUplift(
-            flag=flag,
-            n_rows=n,
-            mean_ndcg_off=off_total / n if n else 0.0,
-            mean_ndcg_on=on_total / n if n else 0.0,
-        ))
+        # Compute baseline once per row, then reuse across all flag arms.
+        baseline_scores = [_row_ndcg(row, _default_k(row), {}, tmp_root) for row in rows]
+        n = len(rows)
+        for flag, kwargs_fn in FLAG_KWARGS.items():
+            kwargs_on = kwargs_fn()
+            on_scores = [_row_ndcg(row, _default_k(row), kwargs_on, tmp_root) for row in rows]
+            off_total = sum(baseline_scores)
+            on_total = sum(on_scores)
+            out.append(FlagUplift(
+                flag=flag,
+                n_rows=n,
+                mean_ndcg_off=off_total / n if n else 0.0,
+                mean_ndcg_on=on_total / n if n else 0.0,
+            ))
🤖 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/retrieve_uplift_runner.py` around lines 214 - 232, The loop recomputes
the baseline NDCG for every flag and row via _row_ndcg(row, k, {}, tmp_root);
change the logic to compute the baseline once per row and reuse it for all
flags: for each row (and k = _default_k(row)) call baseline_ndcg =
_row_ndcg(row, k, {}, tmp_root) once, then iterate over FLAG_KWARGS items and
compute on_ndcg = _row_ndcg(row, k, kwargs_on, tmp_root) only for the flagged
variant (and if kwargs_on is {} reuse baseline_ndcg), accumulating off_total
using baseline_ndcg instead of calling _row_ndcg repeatedly; keep producing
FlagUplift objects with mean_ndcg_off based on the cached baseline values.
🤖 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 `@tests/retrieve_uplift_runner.py`:
- Around line 85-104: The function _belief_from_row incorrectly reads
b["content"] (causing KeyError on real corpus rows where the field is "text");
update _belief_from_row to read b["text"] and map it into the Belief.content
field (i.e., set content=b["text"]) and keep content_hash and other fields
unchanged; also update the synthetic test data in
tests/test_retrieve_uplift_runner.py to use "text" (not "content") so tests
reflect the schema-validated corpus shape and will catch regressions.

In `@tests/test_retrieve_uplift_runner.py`:
- Around line 61-82: The test uses synthetic belief dicts with key "content"
which will break once _belief_from_row is fixed to read b["text"]; update the
synthetic row in test_run_per_flag_uplift_covers_all_flags so each belief uses
"text" instead of "content" (the row passed into run_per_flag_uplift), and scan
the test for any other synthetic beliefs to make the same change; keep
references to run_per_flag_uplift and FLAG_KWARGS unchanged.

---

Nitpick comments:
In `@tests/retrieve_uplift_runner.py`:
- Around line 214-232: The loop recomputes the baseline NDCG for every flag and
row via _row_ndcg(row, k, {}, tmp_root); change the logic to compute the
baseline once per row and reuse it for all flags: for each row (and k =
_default_k(row)) call baseline_ndcg = _row_ndcg(row, k, {}, tmp_root) once, then
iterate over FLAG_KWARGS items and compute on_ndcg = _row_ndcg(row, k,
kwargs_on, tmp_root) only for the flagged variant (and if kwargs_on is {} reuse
baseline_ndcg), accumulating off_total using baseline_ndcg instead of calling
_row_ndcg repeatedly; keep producing FlagUplift objects with mean_ndcg_off based
on the cached baseline values.
🪄 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

Run ID: 5a00d32c-105d-46d3-b896-bd6071973478

📥 Commits

Reviewing files that changed from the base of the PR and between e1c3dcb and 6dc4411.

📒 Files selected for processing (5)
  • tests/bench_gate/test_retrieve_uplift.py
  • tests/corpus/v2_0/README.md
  • tests/retrieve_uplift_runner.py
  • tests/test_corpus_schema.py
  • tests/test_retrieve_uplift_runner.py

Comment on lines +85 to +104
def _belief_from_row(b: dict) -> Belief: # type: ignore[type-arg]
"""Build a Belief from a corpus row's belief dict.

Required: `id`, `content`. Optional: `type`, `alpha`, `beta`.
Defaults match the factual/agent-inferred shape.
"""
return Belief(
id=b["id"],
content=b["content"],
content_hash=f"corpus:{b['id']}",
alpha=float(b.get("alpha", 1.0)),
beta=float(b.get("beta", 1.0)),
type=b.get("type", BELIEF_FACTUAL),
lock_level=LOCK_NONE,
locked_at=None,
demotion_pressure=0,
created_at=_TS,
last_retrieved_at=None,
origin=ORIGIN_AGENT_INFERRED,
)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical | ⚡ Quick win

_belief_from_row reads b["content"] but corpus beliefs have a "text" field — KeyError on real corpus data.

The schema validator in test_corpus_schema.py (the "list[belief]" spec) enforces b["text"] on every corpus row. Other BFS modules also document belief objects as {"id": str, "text": str} (README line 120). The harness maps this corpus field to Belief.content (the internal model attribute name), so the key to read is "text", not "content".

The unit test in test_retrieve_uplift_runner.py (lines 68–71) uses "content" in its synthetic beliefs, which means the tests pass locally while silently masking the mismatch — the integration against real corpus rows will raise KeyError: 'content'.

🐛 Proposed fix
 def _belief_from_row(b: dict) -> Belief:
     return Belief(
         id=b["id"],
-        content=b["content"],
+        content=b["text"],
         content_hash=f"corpus:{b['id']}",

And in tests/test_retrieve_uplift_runner.py lines 68–71, update synthetic beliefs to match the schema-validated shape:

     "beliefs": [
-        {"id": "b1", "content": "the memory store persists beliefs"},
-        {"id": "b2", "content": "the configuration file lives at /etc"},
-        {"id": "b3", "content": "the memory store uses sqlite"},
+        {"id": "b1", "text": "the memory store persists beliefs"},
+        {"id": "b2", "text": "the configuration file lives at /etc"},
+        {"id": "b3", "text": "the memory store uses sqlite"},
     ],
🤖 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/retrieve_uplift_runner.py` around lines 85 - 104, The function
_belief_from_row incorrectly reads b["content"] (causing KeyError on real corpus
rows where the field is "text"); update _belief_from_row to read b["text"] and
map it into the Belief.content field (i.e., set content=b["text"]) and keep
content_hash and other fields unchanged; also update the synthetic test data in
tests/test_retrieve_uplift_runner.py to use "text" (not "content") so tests
reflect the schema-validated corpus shape and will catch regressions.

Comment on lines +61 to +82
def test_run_per_flag_uplift_covers_all_flags() -> None:
"""Hypothesis: the harness reports one row per registered flag.
Falsifiable if a flag is silently dropped."""
row = {
"id": "ru-test-001",
"query": "memory store",
"k": 3,
"beliefs": [
{"id": "b1", "content": "the memory store persists beliefs"},
{"id": "b2", "content": "the configuration file lives at /etc"},
{"id": "b3", "content": "the memory store uses sqlite"},
],
"edges": [],
"expected_top_k": ["b1", "b3"],
}
results = run_per_flag_uplift([row])
flags = {r.flag for r in results}
assert flags == set(FLAG_KWARGS.keys())
for r in results:
assert r.n_rows == 1
assert 0.0 <= r.mean_ndcg_off <= 1.0
assert 0.0 <= r.mean_ndcg_on <= 1.0

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Synthetic beliefs use "content" instead of "text" — masks the corpus field name bug.

Once _belief_from_row is corrected to read b["text"] (see the critical issue on retrieve_uplift_runner.py line 93), these synthetic beliefs must also be updated to "text" or the unit test will start failing with a KeyError. See the proposed diff in that comment for the coordinated fix.

🤖 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_retrieve_uplift_runner.py` around lines 61 - 82, The test uses
synthetic belief dicts with key "content" which will break once _belief_from_row
is fixed to read b["text"]; update the synthetic row in
test_run_per_flag_uplift_covers_all_flags so each belief uses "text" instead of
"content" (the row passed into run_per_flag_uplift), and scan the test for any
other synthetic beliefs to make the same change; keep references to
run_per_flag_uplift and FLAG_KWARGS unchanged.

@robotrocketscience
robotrocketscience merged commit 6dc4411 into main May 5, 2026
28 of 33 checks passed
@robotrocketscience
robotrocketscience deleted the feat/issue-154-retrieve-bench-harness branch May 5, 2026 16:00
robotrocketscience added a commit that referenced this pull request May 5, 2026
The v1.7 row was stale: components #149/#150/#152/#153/#216 are all
merged on main and reachable via opt-in feature flags
(use_signed_laplacian, use_heat_kernel, use_hrr_structural in
[retrieval] of .aelfrice.toml). Updates the row to reflect ship
state.

The default-on flip (#154) is deferred. The retrieve-uplift bench
harness (#403/#425) measured +0.6010 NDCG@k uplift for
use_bm25f_anchors on the v0.1 fixture, but a follow-up smoke test
exposed a stemming gap: BM25F's lowercase-tokenize-only path misses
matches that FTS5's Porter stemming catches (banana vs bananas).
The +0.6010 number was correct for exact-token queries; the
production cost on natural-language queries that stem-differ from
content is not yet quantified. Until that's measured, leaving
v1.7 components opt-in keeps the v1.6 retrieval characteristic
intact.

v2.0 row tightened to call out v1.7 default-on flip as the prereq
for the reproducibility-cut tag.
robotrocketscience added a commit that referenced this pull request May 5, 2026
The v1.7 row was stale: components #149/#150/#152/#153/#216 are all
merged on main and reachable via opt-in feature flags
(use_signed_laplacian, use_heat_kernel, use_hrr_structural in
[retrieval] of .aelfrice.toml). Updates the row to reflect ship
state.

The default-on flip (#154) is deferred. The retrieve-uplift bench
harness (#403/#425) measured +0.6010 NDCG@k uplift for
use_bm25f_anchors on the v0.1 fixture, but a follow-up smoke test
exposed a stemming gap: BM25F's lowercase-tokenize-only path misses
matches that FTS5's Porter stemming catches (banana vs bananas).
The +0.6010 number was correct for exact-token queries; the
production cost on natural-language queries that stem-differ from
content is not yet quantified. Until that's measured, leaving
v1.7 components opt-in keeps the v1.6 retrieval characteristic
intact.

v2.0 row tightened to call out v1.7 default-on flip as the prereq
for the reproducibility-cut tag.
robotrocketscience added a commit that referenced this pull request May 5, 2026
The v1.7 row was stale: components #149/#150/#152/#153/#216 are all
merged on main and reachable via opt-in feature flags
(use_signed_laplacian, use_heat_kernel, use_hrr_structural in
[retrieval] of .aelfrice.toml). Updates the row to reflect ship
state.

The default-on flip (#154) is deferred. The retrieve-uplift bench
harness (#403/#425) measured +0.6010 NDCG@k uplift for
use_bm25f_anchors on the v0.1 fixture, but a follow-up smoke test
exposed a stemming gap: BM25F's lowercase-tokenize-only path misses
matches that FTS5's Porter stemming catches (banana vs bananas).
The +0.6010 number was correct for exact-token queries; the
production cost on natural-language queries that stem-differ from
content is not yet quantified. Until that's measured, leaving
v1.7 components opt-in keeps the v1.6 retrieval characteristic
intact.

v2.0 row tightened to call out v1.7 default-on flip as the prereq
for the reproducibility-cut tag.
This was referenced May 5, 2026
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