Repository navigation
graphify path: non-deterministic shortest-path route + calls label not backed by any stored edge #2074
Description
Summary
graphify path "<A>" "<B>" returns a different shortest path on repeated, identical invocations against the same unmodified graph.json — and one of the observed paths displayed a --calls--> edge label that does not correspond to any stored edge with relation: "calls" in the graph data (there were zero calls-relation edges in the entire graph).
Steps to reproduce
- Build a graph with
/graphify(code + docs + papers corpus, ~490 nodes / ~1300 edges after build). - Run:
graphify path "<node_id_A>" "<node_id_B>"where the shortest path is 4 hops with more than one route of equal length available. - Run the exact same command again (no changes to
graph.jsonin between).
Observed
Run 1:
Shortest path (4 hops):
<A> --rationale_for [EXTRACTED]--> ProductArgument <--references [EXTRACTED]-- ShuffleArgument --calls [EXTRACTED]--> ShuffleConsistencyArgument <--rationale_for [EXTRACTED]-- <B>
Run 2 (same command, same graph.json, no edits in between):
Shortest path (4 hops):
<A> --rationale_for [EXTRACTED]--> ProductArgument --shares_data_with [EXTRACTED]--> VectorCommitment <--references [EXTRACTED]-- MultiExpArgumentSqrt <--rationale_for [EXTRACTED]-- <B>
Two different routes, both length 4, returned on identical repeated calls.
Additionally, when I went looking in graph.json for the edge that produced the --calls--> label from Run 1 (to fix what I initially assumed was a bad EXTRACTED edge), I found:
- Zero edges anywhere in the graph with
"relation": "calls". - Zero direct edges of any relation between the two class-level nodes (
ShuffleArgument,ShuffleConsistencyArgument) shown adjacent in that path.
So the --calls--> shown in the CLI path output does not appear to come verbatim from a stored edge's relation field — it looks like either a display-layer synthesized/fallback label, or the path traversal is silently passing through an intermediate node (e.g. a shared/ambiguous generic node like .prove() or .verify() that appears at multiple source locations) whose real relation got mislabeled or collapsed in the printed output.
Why this matters
Users (and the agent driving /graphify) reasonably treat EXTRACTED-confidence output as ground truth per the skill's own Honesty Rules ("never invent an edge"). A shortest-path command that (a) isn't deterministic between identical calls, and (b) can print a relation label that doesn't trace back to any real stored edge, undermines that guarantee specifically for the path command's output — someone could act on --calls--> as fact when no such edge exists.
Environment
- graphifyy 0.9.21
- Windows 11, Git Bash, Python via
uv tool - Graph: ~490 nodes, ~1300 edges, 21 communities, undirected
Suggested fix directions
- Make
path's tie-breaking among equal-length shortest paths deterministic (e.g. stable sort by node id). - When printing an edge in the path, read the relation directly from the stored edge object rather than any synthesized/fallback label, and if multiple parallel edges exist between the same two nodes, either print all relations or pick deterministically (not silently pick one that doesn't match what's stored).
Activity
HerenderKumar commentedon Jul 21, 2026
I looked into this issue and I think there are actually two separate problems here.
First- the non-deterministic path. When we run graphify path for the same source and target on the same graph, it gives a different route sometimes. This usually happens when there is more than one shortest path of equal length, and the code is just picking one of them based on whatever order the nodes/edges come in. That order is not fixed between runs, so the answer keeps changing. The fix would be to make the tie-breaking stable — for example sort the options by node id before choosing, so the same shortest path is returned every time.
Second- the calls label problem. The path output is showing a calls relation on a hop, but there is no such edge actually stored in graph.json for it. So the label is coming from somewhere else (like a name/text match) instead of the real edge data. This is more serious because the output is showing a relationship that does not exist in the graph.
Conclusively, one is a stable-ordering issue, the other is the renderer showing a label not backed by a stored edge. Little complicated but happy to work on it.
Open for more suggestions on fix.
HerenderKumar commentedon Jul 21, 2026
PR up: #2087
fix(path): deterministic route + honest edge relation, not fabricated…
chore: bump to 0.9.23; changelog for #2062/#2074/#2073/#2068/#2072; d…
safishamsi commentedon Jul 21, 2026
Fixed in v0.9.23:
graphify path(and the MCP shortest_path tool) now traverse a sorted, materialized undirected graph so the route is deterministic across runs, and each hop shows the edge's actual stored relation (falling back torelated) rather than a fabricatedcalls. https://github.com/Graphify-Labs/graphify/releases/tag/v0.9.23Merge upstream/v8 into v8-custom (0.9.20 -> 0.9.24)
Squashed commit of the following:
Merge v8 into falkordb-backend: re-port FalkorDB onto the new cli.py
fix(path): deterministic route + honest edge relation, not fabricated…
chore: bump to 0.9.23; changelog for Graphify-Labs#2062/Graphify-Labs…
merge: backport upstream query/path correctness fixes (Graphify-Labs#…
proto(cli,serve): port shortest_path to rustworkx (fork prototype)
perf(serve): build the MCP traversal view and shortest-path graphs on…