Skip to content

fix(serve): reserve budget for EDGE lines so query answers keep relations - #2376

Open
nicknikolakakis wants to merge 1 commit into
Graphify-Labs:v8from
nicknikolakakis:fix/query-edge-budget-starvation
Open

fix(serve): reserve budget for EDGE lines so query answers keep relations#2376
nicknikolakakis wants to merge 1 commit into
Graphify-Labs:v8from
nicknikolakakis:fix/query-edge-budget-starvation

Conversation

@nicknikolakakis

@nicknikolakakis nicknikolakakis commented Aug 1, 2026

Copy link
Copy Markdown

The problem

_subgraph_to_text appends every NODE line before the first EDGE line, then cuts the joined string at char_budget. When the nodes alone fill the budget, every edge is dropped and graphify query returns a bare node list. The relations the traversal was run to find never reach the caller.

The truncation notice hid this, because it tallied nodes only:

[!] TRUNCATED: showing 61 of 61 nodes (~2000-token budget).
The answer may be among the 0 cut nodes ...

That output kept all 61 nodes and silently dropped 34 of 60 edges, while reporting 0 cut. It reads as a complete answer.

Reproduction

A 61-node / 60-edge star at the default budget of 2000 renders 61 nodes and 26 of 60 edges, notice claims 0 cut.

The synthetic case above is in the added tests. I also hit this on a private 1381-node documentation graph, where three separate 2-hop BFS questions traversed 84, 57 and 66 edges respectively and rendered zero of them at the default budget. Raising --budget to 10000 worked around it, which is what pointed at the ordering rather than the traversal.

The fix

When the output is over budget:

  1. Charge the seed block first, so the existing #BUG2 guarantee (the queried symbol always renders, and renders first) is unchanged.
  2. Reserve half of the remainder for EDGE lines.
  3. Give what is left to the remaining NODE lines.

Under budget nothing changes: the output stays byte-identical, still every node followed by every edge. The notice and the end marker now count cut edges alongside cut nodes.

After the fix the same star renders 42 nodes and 47 edges, and the private graph returns 24 to 33 edges per question at the default budget.

Tests

Three added to tests/test_serve.py:

  • test_subgraph_to_text_keeps_edges_when_nodes_overrun_budget
  • test_subgraph_to_text_truncation_notice_counts_cut_edges
  • test_subgraph_to_text_under_budget_output_is_unchanged_by_edge_reserve

The first two fail on v8 without the change and pass with it. The third is a guard that the under-budget path is untouched.

Full suite: 3909 passed, 36 skipped. The 4 failures in tests/test_ollama_retry_cap.py are pre-existing in my environment (ModuleNotFoundError: No module named 'openai', an optional extra) and fail identically on a clean v8 checkout.

Note on the 50/50 split

Half is a deliberate compromise rather than a tuned value. Edges are cheaper per line and carry both endpoint labels, so an argument exists for giving them more. Happy to change the ratio, make it a parameter, or interleave per seed if you prefer a different shape.

…ions

_subgraph_to_text appends every NODE line before the first EDGE line, then
cuts the joined string at char_budget. On any subgraph whose nodes alone
fill the budget, every edge is dropped and the query returns a bare node
list: the relations the traversal was run to find never reach the caller.

The truncation notice hid it. It tallied nodes only, so a cut that kept
all nodes while dropping most edges reported "showing 61 of 61 nodes ...
0 cut nodes", which reads as a complete answer.

Repro on a 61-node/60-edge star at the default budget of 2000: 61 nodes
and 26 of 60 edges rendered, notice claimed 0 cut. On a real 1381-node
docs graph, three 2-hop BFS questions traversed 84, 57 and 66 edges and
rendered zero of them.

Fix: when the output is over budget, charge the seed block first (BUG2
guarantee unchanged), then reserve half the remainder for edges and give
the rest to nodes. Under budget the output is byte-identical, still every
node followed by every edge. The notice and end marker now count cut edges
alongside cut nodes.

Same repro after the fix renders 42 nodes and 47 edges; the real graph
returns 24 to 33 edges per question at the default budget.

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

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

Graphify reviewed this change.

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


Graphify review — findings

This PR reworks the truncation logic in _subgraph_to_text within graphify/serve.py. Instead of building a single flat list of node and edge lines and applying one cut at the character budget, it now tracks node and edge lines separately and, when over budget, reserves a portion of the remaining budget (after seed nodes) specifically for edge lines. The truncation notice and end marker are also updated to report counts of both cut nodes and cut edges rather than nodes only. The diff adds three new tests covering the edge-reservation behavior (edges surviving when nodes overrun the budget, the notice counting cut edges, and under-budget output remaining unchanged). The remaining changed symbols in the test file appear to be incidental to the edit surface rather than substantive modifications.

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

Analysis details — impact, health, verification

Impact & health

Graphify review

Impact — 481 functions depend on the 292 functions this change touches.

Health — this change adds coupling hotspots:

  • worse: _subgraph_to_text() — 20 callers, 1 callees

Verification — 481 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: 344 function(s) in the blast radius were not formally verified this run

· 1 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