Skip to content

NodeRef lacks edge-presence signals — agent cannot see client/producer/route declarations without describe #162

@HumanBean17

Description

@HumanBean17

Summary

When neighbors or find returns a list of method Symbols as NodeRef objects, the agent has no way to know which of those methods declare clients, producers, or expose routes without calling describe(id=...) on each one individually. The NodeRef schema carries only structural identity fields:

class NodeRef(BaseModel):
    id: str
    kind: Literal["symbol", "route", "client", "producer"]
    fqn: str
    symbol_kind: str | None = None
    microservice: str | None = None
    module: str | None = None
    role: str | None = None

There is no capabilities, outbound_edge_kinds, or similar field that would signal "this method also declares an HTTP client" or "this method exposes a route."

Problem

Consider a typical agent workflow:

  1. search("payment processing") → gets a class FQN
  2. neighbors([class_id], 'out', ['DECLARES']) → gets 15 method NodeRefs
  3. Agent now must decide which methods are interesting for cross-service call tracing

At step 3, the agent has no signal. It must either:

  • Call describe on all 15 methods (expensive, 15 tool calls)
  • Guess based on method names (unreliable)
  • Go back to the class-level describe and use the edge_summary rollup keys (requires the agent to have called describe first, which it may not have done)

If the agent missed the class-level describe call (e.g., it arrived at the class via find or neighbors from another class), it will never learn that one of the methods declares an HTTP client.

Proposed fix

Add a lightweight edge-presence signal to NodeRef for symbol-kind nodes. The field should indicate which interesting outbound edge types exist on the node, without requiring a graph re-query at NodeRef construction time.

Candidate shapes (pick one):

Option A — capabilities field (reuse existing graph data):

class NodeRef(BaseModel):
    ...
    capabilities: list[str] | None = None  # e.g. ["HTTP_CLIENT", "MESSAGE_PRODUCER", "MESSAGE_LISTENER"]

This already exists on the Symbol node in Kuzu. NodeRef just doesn't surface it.

Option B — outbound_edges set (computed at query time):

class NodeRef(BaseModel):
    ...
    has_outbound: list[str] | None = None  # e.g. ["DECLARES_CLIENT", "EXPOSES"]

This requires an extra query per node, which may be too expensive for batch results.

Option A is preferred because capabilities is already stored on each Symbol row and can be projected without additional queries. The indexer already tags methods with capabilities like HTTP_CLIENT, MESSAGE_PRODUCER, MESSAGE_LISTENER, SCHEDULED_TASK, etc. (see ast_java.py capability inference). Surfacing this in NodeRef gives the agent enough signal to prioritize which methods to describe in detail.

Scope

  • Add capabilities (or equivalent) to NodeRef for symbol-kind nodes
  • Populate it from the existing graph data in _node_ref_from_row
  • Update NodeRef schema description in server.py
  • No re-index required (data already exists in the graph)
  • This is a schema change to the MCP tool output surface

What this does NOT do

  • Does not add edge_summary to NodeRef (that belongs on NodeRecord in describe)
  • Does not add per-row hints (that is a separate design axis)
  • Does not change the graph schema or require re-indexing
  • Does not affect NodeRef for route/client/producer kinds (those already carry identifying fields like client_kind, producer_kind, framework)

References

  • mcp_v2.py lines 261–268 (NodeRef definition)
  • mcp_v2.py lines 524–555 (_node_ref_from_row)
  • ast_java.py — capability inference (_METHOD_ANN_TO_CAPABILITY, _TYPE_ANN_TO_CAPABILITY, etc.)
  • java_ontology.pyVALID_CAPABILITIES set

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions