Skip to content

Feat/issue 151 heat kernel composition - #316

Closed
robotrocketscience wants to merge 5 commits into
mainfrom
feat/issue-151-heat-kernel-composition
Closed

Feat/issue 151 heat kernel composition#316
robotrocketscience wants to merge 5 commits into
mainfrom
feat/issue-151-heat-kernel-composition

Conversation

@robotrocketscience

@robotrocketscience robotrocketscience commented Apr 29, 2026

Copy link
Copy Markdown
Owner

Summary

Linked issues

-->

Type of change

  • feat: — new feature
  • fix: — bug fix
  • perf: — performance improvement
  • refactor: — code restructure with no behavior change
  • test: — test-only change
  • docs: — documentation-only change
  • build: — build system / dependency / lockfile change
  • ci: — CI workflow / hook change
  • release: — version bump / release tag
  • chore: — narrow housekeeping

Verification

  • uv run pytest tests/ -x -q — all green
  • uv run pyright src/ — strict, no new errors
  • uv run aelf --help — surface unchanged (or change documented)
  • CHANGELOG entry added under [Unreleased] (if user-visible)
  • Docs updated (if surface or behavior changed)

Test plan

Notes for reviewer

Summary by Sourcery

Integrate heat-kernel graph authority into retrieval ranking behind a feature flag and wire it through retrieval, benchmarking, and CLI surfaces while preserving heat-off behavior.

New Features:

  • Add optional heat-kernel authority term to L1 retrieval ranking via eigenbasis-backed propagation combined with BM25 and posterior scores.
  • Expose heat-kernel composition controls in retrieve()/retrieve_with_tiers and CLI bench command, with configuration resolved through existing enablement helpers.

Enhancements:

  • Extend posterior ranking benchmarks to optionally build and reuse eigenbasis caches per store, enabling evaluation of heat-kernel effects on MRR and ECE.
  • Add an AC6 performance harness to measure heat-kernel overhead at 50k beliefs and validate latency budgets.

Documentation:

  • Document Slice 2 heat-kernel composition, feature flag behavior, cost model, and degradation paths in the Bayesian ranking design doc.

Tests:

  • Add retrieval-side tests for heat-kernel composition covering flag-off equivalence, empty and stale eigenbasis fallbacks, cold beliefs, and authority-driven re-ranking behavior.

Threads `eigenbasis_cache` + `heat_kernel_enabled` through `retrieve()`
and `retrieve_with_tiers()` into `_l1_hits`. When the cache holds a
non-stale eigenbasis and the flag is on, the L1 rerank dispatches to
`combine_log_scores(bm25, heat, posterior_mean)` from #150's
graph_spectral module instead of `partial_bayesian_score`. Heat
propagation is one `eigvecs.T @ seeds` matvec per query, indexed by
the eigenbasis row order.

Heat-off path is byte-identical: kwargs default to None, the flag
defaults False, and `_l1_hits` falls back to `partial_bayesian_score`
when the cache is None / stale / empty / has no overlap with the L1
hit set. AC4 (flag-on identical to flag-off when no feedback exists)
is preserved by the no-overlap fallback.

Tests + bench wedge land in the next commit.
Five tests covering:
- heat-off byte-identical to Slice 1 contract (AC4)
- empty eigenbasis graceful degrade (AC4 fallback)
- authority signal changes ranking on connected graph
- cold-belief floor (newly inserted belief gets HEAT_SCORE_FLOOR)
- store-mutation invalidation flips is_stale and degrades

All five run against small fixtures (<=10 beliefs, K=200 eigenbasis); no
N=50k bench in this commit (covered by the bench wedge in the next).
…2 of #151

Threads heat_kernel: bool through run() -> run_multi_seed() and
_build_ece_observations(). When True, each per-seed retrieve() gets a
fresh GraphEigenbasisCache with .build() called against that seed's
store, rebuilt on stale (apply_feedback mutates the store -> invalidates
cache). Heat-off remains the default; existing bench output is
byte-identical when the flag is omitted.

Smoke run on default.jsonl (n_seeds=2): MRR uplift +0.0000, ECE 0.1373,
overall FAIL. Numerically identical to heat-off on this fixture set
because the synthetic corpus carries no edges, so the heat term
degrades to the floor for every belief and the ranking collapses to
the BM25-only contract — graceful-degrade path is exercised end-to-end.
A graph-bearing fixture set (slice 3 sweep) is what would surface real
uplift.
Reproducible 50k-belief / 5k-edge benchmark for AC6. Times
retrieve() heat-off vs heat-on with eigenbasis cache prebuilt,
30 calls (5 warmup, 25 measured), reports median/p90/max.

Used to renegotiate AC6 from \xe2\x89\xa41ms / \xe2\x89\xa410ms to \xe2\x89\xa410ms / \xe2\x89\xa425ms
against the measured retrieve() baseline (~7ms heat-off,
~19ms heat-on at N=50k).
@sourcery-ai

sourcery-ai Bot commented Apr 29, 2026

Copy link
Copy Markdown

Reviewer's Guide

Implements Slice 2 of issue #151 by adding heat-kernel graph authority composition into L1 retrieval scoring behind a feature flag, wiring eigenbasis caches through retrieval APIs, extending benchmarking to measure heat-on vs heat-off behavior, documenting the new composition, and adding tests to validate graceful degradation and ranking effects.

Sequence diagram for retrieve() with heat-kernel composition

sequenceDiagram
    actor User
    participant CLI as CLI_bench_posterior_residual
    participant Bench as PosteriorRankingBench
    participant Store as MemoryStore
    participant Cache as GraphEigenbasisCache
    participant Ret as RetrievalModule

    User->>CLI: aelf bench posterior-residual --heat-kernel
    CLI->>Bench: run_multi_seed(fixtures, heat_kernel=True)
    Bench->>Bench: run_single_seed(..., heat_kernel=True)
    Bench->>Store: _build_store(fixture, seed)

    alt heat_kernel=True
        Bench->>Cache: GraphEigenbasisCache(store, path)
        Bench->>Cache: build()
    end

    loop per_round
        Bench->>Bench: _refresh_caches()
        Bench->>Cache: is_stale()
        alt cache stale and heat_kernel=True
            Bench->>Cache: build()
        end

        Bench->>Ret: retrieve(store, query, l1_limit, bfs_enabled=False, posterior_weight=None, heat_kernel_enabled=True, eigenbasis_cache=Cache)

        activate Ret
        Ret->>Ret: is_heat_kernel_enabled(heat_kernel_enabled)
        Ret->>Ret: resolve_use_bm25f_anchors()
        Ret->>Ret: resolve_posterior_weight()
        Ret->>Ret: bfs_on, bm25f_on, heat_on

        Ret->>Ret: _l1_hits(store, query, l1_limit, posterior_weight, use_bm25f_anchors=bm25f_on, bm25f_cache, eigenbasis_cache=Cache, heat_kernel_on=heat_on)

        activate Ret
        Ret->>Ret: heat_active = heat_kernel_on and eigenbasis_cache not None and not eigenbasis_cache.is_stale() and eigenbasis_cache.eigvals not None

        alt use_bm25f_anchors=True
            Ret->>Store: list_beliefs_for_bm25f()
            Store-->>Ret: beliefs_with_raw_scores
        else use_bm25f_anchors=False (FTS5)
            Ret->>Store: search_beliefs_scored(query, l1_limit)
            Store-->>Ret: beliefs_with_bm25_raw
        end

        alt heat_active and posterior_weight==0.0
            Ret->>Ret: bm25_pos_by_id = {belief_id: bm25_pos}
            Ret->>Ret: heat_map = _heat_by_id(Cache, bm25_pos_by_id)
            Ret->>Ret: combine_log_scores(bm25_pos, heat, posterior)
        else heat_active and posterior_weight>0.0
            Ret->>Ret: bm25_pos_by_id = {belief_id: bm25_pos}
            Ret->>Ret: heat_map = _heat_by_id(Cache, bm25_pos_by_id)
            Ret->>Ret: combine_log_scores(bm25_pos, heat, posterior)
        else not heat_active
            Ret->>Ret: partial_bayesian_score(bm25_raw, alpha, beta, posterior_weight)
        end

        Ret-->>Bench: ranked_l1_beliefs
        deactivate Ret

        Bench-->>Bench: apply synthetic feedback
    end

    Bench->>Store: close()
    Bench-->>CLI: MRR and ECE results
    CLI-->>User: benchmark report
Loading

File-Level Changes

Change Details Files
Add heat-kernel authority term to L1 reranking behind a feature flag, composing it with BM25/BM25F and posterior scores while preserving byte-identical behavior when disabled.
  • Introduce _heat_by_id helper to run a single heat-kernel propagation pass over cached eigenbasis rows seeded from positive BM25 magnitudes and return per-belief heat scores keyed by id.
  • Extend _l1_hits to accept eigenbasis_cache and heat_kernel_on flags, compute heat_active based on cache freshness, and, when active, combine BM25/BM25F, heat, and posterior terms via combine_log_scores with default weights and safe floors; otherwise fall back to existing partial_bayesian_score behavior.
  • Ensure FTS5 and BM25F code paths normalize BM25 scores into positive magnitudes for seeding and composition, handle zero-posterior_weight as a special case for heat-on, and maintain the original retrieval ordering when the heat path is inactive or short-circuited.
src/aelfrice/retrieval.py
Plumb heat-kernel controls and eigenbasis caches through public retrieval APIs and benchmark harnesses, and expose a CLI flag to toggle the feature for posterior-residual benchmarks.
  • Add heat_kernel_enabled and eigenbasis_cache parameters to retrieve and retrieve_with_tiers, resolve the feature flag via is_heat_kernel_enabled, and thread these into _l1_hits.
  • Update posterior ranking MRR and ECE benchmark runners to optionally construct per-store GraphEigenbasisCache instances in temp directories, rebuild them on staleness during synthetic feedback rounds, and pass them plus the heat-kernel flag into retrieve.
  • Extend bench posterior-residual CLI subcommand with a --heat-kernel boolean that toggles the new composition path in the benchmark run pipeline.
src/aelfrice/retrieval.py
benchmarks/posterior_ranking/mrr_uplift.py
benchmarks/posterior_ranking/run.py
src/aelfrice/cli.py
Document heat-kernel log-additive composition, feature flag semantics, performance expectations, and graceful-degradation behavior for Slice 2 of #151.
  • Describe the new three-term log-additive scoring formula with heat-kernel authority, including default weights, score clamping, and how the graph_spectral.combine_log_scores function is used.
  • Define the heat-kernel feature flag resolution order (env, kwarg, TOML, default-OFF) and clarify that the heat term is not constructed when the flag is off, preserving Slice 1 behavior.
  • Explain perf cost assumptions, degrade paths when no usable eigenbasis is available, treatment of newly inserted beliefs missing from the cache, and how the posterior-residual benchmark wedge is used to evaluate the feature.
docs/bayesian_ranking.md
Add targeted tests for heat-kernel composition wiring and graceful degradation in retrieval, plus an AC6 performance harness at N=50k to measure heat-on overhead.
  • Introduce tests/test_posterior_ranking_heat.py to build small in-memory stores with and without graph structure, then assert: heat-off results are byte-identical with and without the flag; unbuilt or stale eigenbases fall back; heat-on reorders authority vs isolated beliefs; newly inserted beliefs remain rankable; and cache invalidation on store mutation degrades to the non-heat path.
  • Add benchmarks/posterior_ranking/ac6_50k.py to construct a 50k-belief, sparse-edge store, build an eigenbasis cache, and time retrieve with heat-on vs heat-off across multiple queries, checking measured medians against the AC6 budget.
  • Ensure temporary directories and caches are cleaned up and stores closed in both tests and benchmarks to avoid resource leakage across runs.
tests/test_posterior_ranking_heat.py
benchmarks/posterior_ranking/ac6_50k.py

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 Apr 29, 2026

Copy link
Copy Markdown

Warning

Rate limit exceeded

@robotrocketscience has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 12 minutes and 9 seconds before requesting another review.

To keep reviews running without waiting, you can enable usage-based add-on for your organization. This allows additional reviews beyond the hourly cap. Account admins can enable it under billing.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 16dd6de0-2e5c-4555-84ed-fb9fafb5d599

📥 Commits

Reviewing files that changed from the base of the PR and between 2a8f47f and 1c9ec2e.

📒 Files selected for processing (7)
  • benchmarks/posterior_ranking/ac6_50k.py
  • benchmarks/posterior_ranking/mrr_uplift.py
  • benchmarks/posterior_ranking/run.py
  • docs/bayesian_ranking.md
  • src/aelfrice/cli.py
  • src/aelfrice/retrieval.py
  • tests/test_posterior_ranking_heat.py
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/issue-151-heat-kernel-composition

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
Review rate limit: 0/1 reviews remaining, refill in 12 minutes and 9 seconds.

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 found 1 issue, and left some high level feedback:

  • The heat-kernel rerank logic in _l1_hits is duplicated between the BM25F and FTS5 branches; consider extracting a small helper that takes the BM25 magnitude per belief and returns the combined score to reduce divergence risk between the two paths.
  • In the perf and benchmark helpers (ac6_50k.py, mrr_uplift.py, _build_ece_observations), you manually call TemporaryDirectory().cleanup(); using a with TemporaryDirectory(...) as tmp: context manager would simplify lifecycle management and avoid accidental leaks if early returns are added later.
  • In benchmarks/posterior_ranking/ac6_50k.py you construct edges with the string literal "SUPPORTS"; using the shared EDGE_SUPPORTS constant there would keep the edge-type vocabulary consistent with the rest of the codebase.
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- The heat-kernel rerank logic in `_l1_hits` is duplicated between the BM25F and FTS5 branches; consider extracting a small helper that takes the BM25 magnitude per belief and returns the combined score to reduce divergence risk between the two paths.
- In the perf and benchmark helpers (`ac6_50k.py`, `mrr_uplift.py`, `_build_ece_observations`), you manually call `TemporaryDirectory().cleanup()`; using a `with TemporaryDirectory(...) as tmp:` context manager would simplify lifecycle management and avoid accidental leaks if early returns are added later.
- In `benchmarks/posterior_ranking/ac6_50k.py` you construct edges with the string literal `"SUPPORTS"`; using the shared `EDGE_SUPPORTS` constant there would keep the edge-type vocabulary consistent with the rest of the codebase.

## Individual Comments

### Comment 1
<location path="docs/bayesian_ranking.md" line_range="231" />
<code_context>
+
+### Bench wedge
+
+`aelf bench posterior-residual --heat-kernel` runs the MRR + ECE harness with the flag flipped on. Each per-seed `retrieve()` gets a fresh `GraphEigenbasisCache` built against that seed's in-memory store and rebuilt on stale (the synthetic feedback stream mutates the store after every round). Without `--heat-kernel`, output is byte-identical to today.
+
+### What Slice 2 still doesn't ship
</code_context>
<issue_to_address>
**suggestion (typo):** Clarify the phrase "rebuilt on stale" in the Bench wedge description.

Consider rephrasing to something like "rebuilt when stale" or "rebuilt once stale" to make the cache rebuild condition clearer.

```suggestion
`aelf bench posterior-residual --heat-kernel` runs the MRR + ECE harness with the flag flipped on. Each per-seed `retrieve()` gets a fresh `GraphEigenbasisCache` built against that seed's in-memory store and rebuilt when stale (the synthetic feedback stream mutates the store after every round). Without `--heat-kernel`, output is byte-identical to today.
```
</issue_to_address>

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.

Comment thread docs/bayesian_ranking.md

### Bench wedge

`aelf bench posterior-residual --heat-kernel` runs the MRR + ECE harness with the flag flipped on. Each per-seed `retrieve()` gets a fresh `GraphEigenbasisCache` built against that seed's in-memory store and rebuilt on stale (the synthetic feedback stream mutates the store after every round). Without `--heat-kernel`, output is byte-identical to today.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

suggestion (typo): Clarify the phrase "rebuilt on stale" in the Bench wedge description.

Consider rephrasing to something like "rebuilt when stale" or "rebuilt once stale" to make the cache rebuild condition clearer.

Suggested change
`aelf bench posterior-residual --heat-kernel` runs the MRR + ECE harness with the flag flipped on. Each per-seed `retrieve()` gets a fresh `GraphEigenbasisCache` built against that seed's in-memory store and rebuilt on stale (the synthetic feedback stream mutates the store after every round). Without `--heat-kernel`, output is byte-identical to today.
`aelf bench posterior-residual --heat-kernel` runs the MRR + ECE harness with the flag flipped on. Each per-seed `retrieve()` gets a fresh `GraphEigenbasisCache` built against that seed's in-memory store and rebuilt when stale (the synthetic feedback stream mutates the store after every round). Without `--heat-kernel`, output is byte-identical to today.

@robotrocketscience

Copy link
Copy Markdown
Owner Author

[claim:review:Kulili:2026-04-30T01:04:29Z]

@robotrocketscience

Copy link
Copy Markdown
Owner Author

Closing as superseded.

Slice 2 of #151 (heat-kernel composition into log-additive ranking) already shipped via #310 (commit 4220874). This branch was opened against an older main and not rebased; merging it would delete ~3.3k lines of subsequently-landed work — deferred_feedback.py (#256), replay_full_equality (#304), per-turn hook audit log (#314), v2.0 corpus scaffold (#311), and the v2.0 posterior-ranking spec (#277). Net diff: +177 / -3292.

The heat-kernel composition itself is already on main. No re-implementation needed.

Once #313 (auto-rebase workflow) lands, drift this severe will be caught at PR-open time.

@robotrocketscience
robotrocketscience deleted the feat/issue-151-heat-kernel-composition branch April 30, 2026 01:04
@robotrocketscience

Copy link
Copy Markdown
Owner Author

[release:review:Kulili:2026-04-30T01:04:44Z]

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.

1 participant