Skip to content

feat(query): surface bridge nodes reached from two or more seeds - #3285

Open
andytsai821201-spec wants to merge 1 commit into
Graphify-Labs:v8from
andytsai821201-spec:fix/query-bridge-nodes
Open

feat(query): surface bridge nodes reached from two or more seeds#3285
andytsai821201-spec wants to merge 1 commit into
Graphify-Labs:v8from
andytsai821201-spec:fix/query-bridge-nodes

Conversation

@andytsai821201-spec

Copy link
Copy Markdown

Summary

A multi-term question seats several seeds; the node their traversals share is usually what the question is about — yet it renders as an ordinary depth-1 neighbor and, under a tight --budget, loses to a same-layer hub. This adds _bridge_nodes (non-seed nodes reached from ≥2 seeds), names them in the header, and renders them right after the seed block. ~60 lines, no new flag, no LLM, node set unchanged.

Problem

Graph: AuthModule — SessionStore — BillingDB, plus Logger (a hub off AuthModule with eight leaves) and TokenCache off AuthModule. Query "AuthModule BillingDB" seats both entities as seeds. Today at --budget 70:

Traversal: BFS depth=2 | Start: ['BillingDB', 'AuthModule'] | 13 nodes found

NODE BillingDB
NODE AuthModule
NODE Logger              <- hub, same hop layer, more edges
... (truncated — SessionStore cut)

SessionStore is the only node connecting the two things the question named, and it is the one dropped. path handles the exact two-endpoint case, but not a natural-language question that resolves to several seeds.

Fix

  • _bridge_nodes(G, nodes, seeds, depth): walk each seed separately inside the node set the traversal already found (both edge directions, same hop rule as the layering in _subgraph_to_text); return non-seed nodes reached from ≥2 seeds, ordered by seeds-reached then id. It never adds a node the traversal did not find.
  • _query_graph_text: header gains Bridges: [labels] when any exist; bridges are passed to the renderer.
  • _subgraph_to_text(..., bridges=None): bridges render directly after the seed block; everything else keeps today's hop/degree order.

After:

Traversal: BFS depth=2 | Start: ['BillingDB', 'AuthModule'] | Bridges: ['SessionStore'] | 13 nodes found

NODE BillingDB
NODE AuthModule
NODE SessionStore
NODE Logger
...

Byte-identical for single-seed questions: fewer than two seeds ⇒ no bridges, no header entry, unchanged ordering. A seed is never reported as a bridge.

Tests

  • _bridge_nodes: two seeds → the connector; single seed → []; adjacent seeds never report each other.
  • Renderer: bridges= places them immediately after the seeds.
  • End to end: header names SessionStore, and it survives a budget that cuts the hub.
  • Guard: single-seed query output contains no Bridges.

All watched failing first (ImportError / unexpected kwarg / header without Bridges). tests/test_serve.py: 152 passed. Full suite: 5,170 passed; the 15 failures present (test_ollama_retry_cap.py missing openai, test_skillgen.py audit baselines) reproduce identically on pristine v8 in this environment.

Relation to existing work

Complementary to #1783 (external --seed-file: bridges apply to externally supplied seed groups too) and to #1184's ask for structured retrieval primitives over the existing graph — "what connects these entities" is one such primitive. Independent of #3284 (same-layer relevance ordering); the two compose but neither depends on the other.

🤖 Generated with Claude Code

A multi-term question ("AuthModule BillingDB", "what links X to Y") seats
several seeds, and the node their traversals share is usually the answer.
Today it is rendered as just another depth-1 neighbor — and under a tight
`--budget` it loses to a same-layer hub with more edges, so the connective
node is cut while the incidental hub survives. `path` covers the exact
two-endpoint case, but nothing covers a natural-language question that
resolves to several seeds.

Add `_bridge_nodes`: walk each seed separately inside the node set the
traversal already found (both edge directions, same hop rule as the layering
in `_subgraph_to_text`), and return the non-seed nodes reached from two or
more seeds, ordered by seeds-reached then id. It never adds a node the
traversal did not find, so the answer's node set is unchanged.

`_query_graph_text` names them in the header (`Bridges: [...]`) and passes
them to `_subgraph_to_text`, which renders them directly after the seed
block ahead of the hop/degree-ordered rest. No new flag, no LLM, no extra
scoring pass.

Single-seed questions are byte-identical: no bridges, no header entry,
unchanged ordering.

Tests: `_bridge_nodes` (two seeds -> connector; single seed -> empty; a
seed is never reported as a bridge), renderer places bridges after seeds,
end to end header + survival under a budget that cuts the hub, and the
single-seed no-op guard.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

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

Graphify reviewed this change.

Looks safe to merge — no coupling regressions and no blocking issues, checked against the code graph (not a self-assessment).

Formal verification. 2 change(s) tested, no difference found (not proven).


Graphify review — findings

Adds bridge-node detection to graph queries: _bridge_nodes walks each seed's traversal within the already-visited node set and returns the non-seed nodes reached from two or more seeds, ordered by seed-count then id. Multi-term questions now name these connective nodes in a new Bridges: header line and render them in _subgraph_to_text immediately after the seed block, so the node linking the queried entities survives truncation ahead of same-layer hubs. Single-seed or fewer queries get no bridges and the ordering is unchanged.

No blocking issues surfaced. 2 lower-confidence candidates did not survive cross-model review.

Analysis details — impact, health, verification

Impact & health

Graphify review

Impact — 584 functions depend on the 342 functions this change touches.

Health — this change adds coupling hotspots:

  • new: dispatch_command() — 2 callers, 123 callees
  • new: _query_graph_text() — 22 callers, 10 callees
  • new: _score_query() — 15 callers, 5 callees
  • new: _query_terms() — 20 callers, 3 callees
  • new: run_benchmark() — 16 callers, 3 callees
  • new: _build_server() — 2 callers, 16 callees
  • new: _load_graph() — 9 callers, 3 callees
  • new: _query_subgraph_tokens() — 7 callers, 3 callees
  • …and 8 more — each is listed as a finding

Verification — 584 functions in the blast radius were not formally verified this run (proofs are advisory here).

Gate & verification

graphify gate

PASS — objectively clean (no health regressions, tests not run — proofs not run this pass (advisory)). Grounded, not self-assessed.

Advisory (not blocking):

  • verification_scope: 409 function(s) in the blast radius were not formally verified this run

Formal verification

No difference found (not proven): No behavior difference found in \_query\_graph\_text (not a proof).

The verifier ran both versions of \_query\_graph\_text on many inputs and saw identical behavior every time. Strong evidence the change is safe, but evidence, not a proof.

Guarantee: Empirical: differential testing (both versions run on many generated inputs). A divergence on an untested input remains possible, so this is 'no counterexample found', not 'proven equivalent'.

Note: An input the sampler did not try could still differ.

No difference found (not proven): No behavior difference found in \_subgraph\_to\_text (not a proof).

The verifier ran both versions of \_subgraph\_to\_text on many inputs and saw identical behavior every time. Strong evidence the change is safe, but evidence, not a proof.

Guarantee: Empirical: differential testing (both versions run on many generated inputs). A divergence on an untested input remains possible, so this is 'no counterexample found', not 'proven equivalent'.

Note: An input the sampler did not try could still differ.

· 16 more finding(s) on lines outside this diff (see the check run).

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