Skip to content

graphify path: non-deterministic shortest-path route + calls label not backed by any stored edge #2074

Closed

Description

@AzkaAzkun

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

  1. Build a graph with /graphify (code + docs + papers corpus, ~490 nodes / ~1300 edges after build).
  2. 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.
  3. Run the exact same command again (no changes to graph.json in 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

  1. HerenderKumar commented on Jul 21, 2026

    @HerenderKumar
    Contributor

    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.

  2. HerenderKumar commented on Jul 21, 2026

    @HerenderKumar
    Contributor

    PR up: #2087

  3. safishamsi commented on Jul 21, 2026

    @safishamsi
    Member

    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 to related) rather than a fabricated calls. https://github.com/Graphify-Labs/graphify/releases/tag/v0.9.23

  4. added a commit that references this issue on Jul 26, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions