Skip to content

feat: ship the PageRank lane the README credits (off by default) - #52

Merged
tonydzi merged 3 commits into
mainfrom
feat/ppr-lane-51
Oct 20, 2026
Merged

tonydzi merged 3 commits into
mainfrom
feat/ppr-lane-51

Conversation

@tonydzi

@tonydzi tonydzi commented Oct 20, 2026 •

Copy link
Copy Markdown
Owner

Mycroft here, Anton's synthetic AI co-founder. This closes the gap the issue names: the README credited the bridge gain to a lane the package did not contain.

Closes #51

What changed

  • src/sqlite_graph_memory/ppr.py: personalized PageRank over the [[wikilink]] file graph (numpy CSR power iteration, alpha 0.85, 20 iters, seeded by the top 15 dense files), RRF fusion (k=60) with the reranker, top 5 reranker positions protected, PPR only reorders the pool. Ported from the maintainer's home module brain_graph.py (live file pulled from the home machine, last written 2026-09-16), same constants.
  • Off by default: SGM_PPR=1 or sgm-ask --ppr. Crash-safe like the graph hop: no edges, a PPR error, a fusion error or a missing module each return the reranker order and write one stderr line.
  • eval/run_eval.py --ppr adds a third mode ppr (graph pool, one full rerank, graph = its top 12, ppr = fused). Without --ppr the output is unchanged.
  • README states plainly that 0.251 -> 0.451 came from the home vault on 2026-09-16 and has not been re-run from the package. CHANGELOG, llms.txt and .env.example list the flag.

Tests

$ python -m pytest tests -q
159 passed in 2.07s

tests/test_ppr.py (18 tests): a hub note plus a 10-note cluster that every PPR seed links to; the reranker's rank-1 hit is not a seed. Measured on that fixture: with the guard (head=5) rank-1 stays at position 1; with head=0 it falls to position 11.

Red proof, guard removed on a copy (PPR_HEAD = 0):

>       assert out[0] == 'answer'
E       AssertionError: assert 'c00' == 'answer'
FAILED tests/test_ppr.py::test_hub_cannot_push_reranker_rank1_out_of_top5
1 failed, 17 passed

Review panel (5 outside models, 4 answered)

Fixed: fusion deduplicated by file name, so a/x.md and b/x.md became one result and the list came out shorter than 12 (new test, red on the first commit). run_eval --ppr now warns when the lane returned nothing for every question, instead of printing ppr rows identical to graph rows.
Checked and rejected: "order used before assignment" (assigned at line 310, used at 328); "negative teleport" (the seed starts at 0 and takes the max, so it cannot go negative; a test for it stayed green on the old code, so it was dropped). Kept as is: same-name notes share one graph node, as in the graph hop, because wikilinks address notes by name; with --ppr --ab both sides get the lane, so the diff still isolates the graph.

Independent review (session P39382), fixed in 7944c18

  • Graph.build kept only the first file per name; now a node (a file name, what a wikilink addresses) carries the links of every file with that name. New test red on the previous commit.
  • An empty lane leaves the ppr column identical to graph; pinned by a test.
  • --ab does not apply the lane, so ab_recall stays vector against graph.
  • README: the bridge numbers match sgm-ask --graph --ppr; the BM25 hybrid and bi-temporal rows now say they live in the home pipeline, not in this package.
  • Deferred: the lane ignores the entity gate (it reorders even when the 1-hop graph is gated off for names).

Not checked

No retrieval numbers were re-run for this PR: the machine that built it has no embedding stack installed, and bench/ does not score this lane yet.

🖥️P · Opus 5.5 · 89706

🤖 Generated with Claude Code

Personalized PageRank over the [[wikilink]] graph, seeded by the dense hits, fused with
the reranker by RRF; the reranker's top 5 positions are never touched. Ported from the
maintainer's home module brain_graph.py (2026-09-16), same constants. Off by default:
SGM_PPR=1 or sgm-ask --ppr. eval/run_eval.py --ppr scores it as a third mode. Any
failure returns the reranker's order.

tests/test_ppr.py: a hub note and its cluster push the reranker's rank-1 hit to
position 11 when the guard is removed (head=0); the guard test goes red then.

Closes #51

Assisted-by: claude-code/claude-opus-5-5
Machine: MacPro192
Account: a@paloalto.ai
Operator: Anton
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Oct 20, 2026 •

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review ✅ Completed 2026-10-20T10:22:50.276945Z dab7d54 PR opened
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@chatgpt-codex-connector chatgpt-codex-connector 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.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: dab7d54187

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread README.md
Comment on lines +332 to +333
numbers above came from the maintainer's home vault and home pipeline on 2026-09-16, and they
have not been re-run from the published package. The code is the same algorithm with the same

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Run the required retrieval regression evaluation

Do not ship this retrieval-ordering change based only on the earlier home-pipeline result: this paragraph explicitly says the published implementation was not evaluated, so there is no evidence that it stays within the repository's 0.01 nDCG@12 regression limit. Run the same frozen gold set before and after enabling this implementation, and run the required eval mutant check before merging.

AGENTS.md reference: AGENTS.md:L45-L57

Useful? React with 👍 / 👎.

Comment thread src/sqlite_graph_memory/ppr.py Outdated
Comment on lines +231 to +234
by_stem = {}
for i, sc in ranked_idx:
by_stem.setdefault(Path(meta[i]['path']).stem.lower(), (i, sc))
stems = list(by_stem)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Preserve distinct files that share a stem

When the candidate pool contains files such as team-a/foo.md and team-b/foo.md, this stem-keyed dictionary silently discards every occurrence after the highest-ranked one before fusion. Such duplicate filenames are explicitly valid in this repository, so enabling PPR can remove a legitimate result—including one from the supposedly protected top five—and substitute a lower-ranked candidate; retain path/index identity through fusion and use stems only for matching PPR votes.

Useful? React with 👍 / 👎.

Comment on lines +108 to +112
for m in meta:
h.update(str(m.get('path', '')).encode('utf-8', 'replace'))
h.update(b'\x1f')
h.update(str(m.get('sha', '')).encode('utf-8', 'replace'))
h.update(b'\x1e')

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Include note contents in the graph cache key

The normal index metadata produced by index_notes.py has no sha field, so this component is always empty and the fingerprint effectively contains only the path sequence. If notes are reindexed in the same process after wikilinks change while paths and chunk counts remain unchanged, build() returns the old cached edges rather than reparsing the notes; include a real content hash or other changing file signature so the graph remains synchronized.

AGENTS.md reference: AGENTS.md:L65-L69

Useful? React with 👍 / 👎.

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

Cursor Bugbot has reviewed your changes using default effort and found 1 potential issue.

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit dab7d54. Configure here.

Comment thread src/sqlite_graph_memory/ppr.py Outdated
tonydzi and others added 2 commits October 20, 2026 11:32
…e lane never ran

Review panel findings on #52:
- fuse_after_rerank deduplicated by file stem, so a/x.md and b/x.md collapsed into one
  result and the list came out shorter than topn. Fusion now works on positions over
  every item; one implementation instead of two (fuse_with_rerank removed).
  tests/test_ppr.py::test_same_name_in_two_folders_stays_two_results is red on the
  previous ppr.py.
- run_eval --ppr: an empty lane on every question makes the ppr rows equal the graph
  rows, so it now prints a warning like the graph hop does.

Assisted-by: claude-code/claude-opus-5-5
Machine: MacPro192
Account: a@paloalto.ai
Operator: Anton
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…tays out of --ab

Independent review (session P39382) on #52:
1. Graph.build kept only the first file per name, so b/index.md gave no edges. A node
   is a file name (what a wikilink addresses) and now carries the links of every file
   with that name. Red on the previous ppr.py. (Fusion keying by name was fixed in
   fdddf5d.)
2. Empty lane leaves the ppr column byte-identical to graph: pinned by a test.
3. --ab no longer applies the lane: ab_recall has no PPR column, so both arms would
   have been logged as plain vector vs graph. One stderr line says so.
4. README: the bridge numbers match `sgm-ask --graph --ppr`; BM25 hybrid and
   bi-temporal demotion rows now say they live in the home pipeline, not the package.
   ppr.py docstring explains 0.225 (diagnosis run) vs 0.251 (final baseline).

Assisted-by: claude-code/claude-opus-5-5
Machine: MacPro192
Account: a@paloalto.ai
Operator: Anton
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

@tonydzi tonydzi left a comment

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

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

Mycroft (P39382): re-checked 7944c18 myself. Same-name repro (a/index.md + b/index.md) now returns 4 of 4; empty lane equals the no-PPR list exactly; --ab leaves ppr_stems empty so both arms skip the lane; README names sgm-ask --graph --ppr as the configuration the bridge numbers belong to and marks the BM25 and bi-temporal rows as home-pipeline only. 159 tests pass locally. Owner merge pinned to this head. Closes #51.

@tonydzi
tonydzi merged commit 75f00e7 into main Oct 20, 2026
9 checks passed
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.

README credits the bridge gain to PPR + RRF, but the package does not ship it

1 participant