You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
The schema description on NodeRecord.edge_summary (mcp_v2.py:127–138) correctly warns: "do not pass them to neighbors(edge_types=…)". Decision #11 in PR #89's propose locked dot-keys as read-only signals, enforced by Pydantic on the neighbors input (_NEIGHBOR_EDGE_TYPES_ADAPTER).
But a real agent run produced this exchange:
Agent calls describe(class_id) → sees DECLARES.DECLARES_CLIENT: 3.
Agent calls neighbors(class_id, direction="out", edge_types=["DECLARES", "DECLARES_CLIENT"]) — note: not the dot-key, just the legitimate atomic edge types.
neighbors returns 4 DECLARES edges (the methods) and 0DECLARES_CLIENT edges, because DECLARES_CLIENT originates from method Symbols, not from the class.
Agent is misled: rollup promised 3 Clients reachable, single-hop traversal returned 0, no error.
This is working as designed. neighbors runs one hop; the rollup is a 2-hop summary projected onto the class for class-grain convenience. The correct walk requires two calls:
The agent has to infer this decomposition from the dot syntax and the schema description prose. Nothing in the tool output mechanically teaches it.
Why this matters
Sibling failure mode to issue #117 (silent-drop filter fields). Different surface, same principle:
A tool surfaces a signal the agent cannot directly consume, with no machine-readable affordance for translating signal → action.
In #117 the agent submits a filter that's silently ignored. Here the agent reads a rollup count, asks the obvious follow-up traversal, gets silent-zero. Both train the agent to distrust the surface.
The cost is one round of agent confusion per workflow that uses rollups — bounded, but consistent, and impossible to debug from the tool surface alone without reading the propose doc.
Three frame options
(A) Stand on the current decision
Rollup is read-only signal. Agent learns the 2-hop decomposition from the dot syntax and schema description.
Pros
No code change. Current frame holds: describe describes, neighbors traverses one hop, dot is convention.
Preserves the "MCP is a graph navigator, not a path-walker" framing — neighbors doesn't grow knowledge about composed paths.
Cons
Failure mode persists. Every agent's first rollup workflow hits the silent-zero, learns from it, and self-corrects. The N+1th agent pays the same tax.
The schema description prose is doing all the teaching work. LLMs often skip prose under context pressure. Inference-required > mechanically-derivable.
Agent's only mechanical signal that the dot-key is composed is the literal dot character — which is fragile pattern-matching.
(B) Surface decomposition in describe
Add a rollup_paths (or similar) field next to edge_summary that machine-readably tells the agent how to traverse each composed key. Example:
New field on DescribeOutput. Backwards-compatible (additive), but expands the contract.
Slight redundancy — the dot-key syntax already encodes the path; rollup_paths makes it explicit. Worth the redundancy if it prevents the silent-zero, debatable otherwise.
Need to decide whether the agent is expected to consume rollup_paths programmatically or just read it as documentation. Affects schema design.
(C) Make neighbors accept dot-notation as traversal shortcut
Flip decision #11 in PR #89: neighbors(edge_types=["DECLARES.DECLARES_CLIENT"]) runs the 2-hop internally and returns the final-hop targets (or both-hop targets, design choice).
Pros
Strongest agent convenience. Symmetric: same key used in describe is consumable by neighbors. No translation step.
Eliminates the silent-zero entirely — the dot-key becomes a first-class traversal primitive.
Composability concern: do we then accept ["DECLARES.DECLARES_CLIENT", "DECLARES.EXPOSES"] in one call? Cross-product results? What about mixing atomic and composed keys? Each becomes a new design question.
Edge attribute semantics get murky: which edge's confidence, strategy, match does the composed result carry? First hop? Last hop? Both?
Result-cardinality contract changes: today neighbors returns one Edge per matched edge. Composed-mode returns one Edge per path, which may duplicate target nodes if multiple paths reach them.
Where (Computer) lands
(B) is the natural pairing with issue #117's principle: make the tool's output self-describing instead of relying on prose-side documentation. (A) is cheap but the failure mode is durable. (C) is powerful but reopens a frame question PR #89 just locked.
But this is a real design choice and worth the same thinking-time as #117. Bundling temptation: both #117 and this one are about "the tool surface promises something the agent can't directly act on." Resist the bundle for now — different surfaces, different decisions, easier to reason about separately. Cross-link only.
Open sub-questions if (B)
Field name: rollup_paths, composed_edges, traversal_hints? Naming carries the intent.
Schema: structured per-key path metadata as above, or simpler {key: human_readable_walk_string}?
Coverage: include rollup_paths for all composed keys, or only the ones with non-zero counts? Latter is symmetric with edge_summary's zero-omission convention.
Consumer model: is the agent expected to mechanically derive neighbors calls from rollup_paths, or is it documentation-grade? Affects whether the schema is fully typed or string-tolerant.
Open sub-questions if (C)
Edge attributes on composed results: which hop's metadata wins?
Cardinality: one Edge per path, or one Edge per distinct target?
Filter applicability: do filters apply to intermediate hops or only the final target?
Mixing: can edge_types contain both atomic and composed keys in the same call?
Depth-2 only, or do we open the door to depth-N composed keys later?
Wait for thinking-time on issue #117 frame question first — the answer there constrains the answer here. If #117 lands strict-frame (or hybrid), (B) is the natural pairing. If #117 lands permissive, (C) becomes more defensible.
What happened
After PR #106 (rollup keys for type Symbols) and PR #110 (override-axis rollup) landed,
describeon a class returns composed dot-keys inedge_summary:The schema description on
NodeRecord.edge_summary(mcp_v2.py:127–138) correctly warns: "do not pass them toneighbors(edge_types=…)". Decision #11 in PR #89's propose locked dot-keys as read-only signals, enforced by Pydantic on theneighborsinput (_NEIGHBOR_EDGE_TYPES_ADAPTER).But a real agent run produced this exchange:
describe(class_id)→ seesDECLARES.DECLARES_CLIENT: 3.neighbors(class_id, direction="out", edge_types=["DECLARES", "DECLARES_CLIENT"])— note: not the dot-key, just the legitimate atomic edge types.neighborsreturns 4DECLARESedges (the methods) and 0DECLARES_CLIENTedges, becauseDECLARES_CLIENToriginates from method Symbols, not from the class.This is working as designed.
neighborsruns one hop; the rollup is a 2-hop summary projected onto the class for class-grain convenience. The correct walk requires two calls:The agent has to infer this decomposition from the dot syntax and the schema description prose. Nothing in the tool output mechanically teaches it.
Why this matters
Sibling failure mode to issue #117 (silent-drop filter fields). Different surface, same principle:
In #117 the agent submits a filter that's silently ignored. Here the agent reads a rollup count, asks the obvious follow-up traversal, gets silent-zero. Both train the agent to distrust the surface.
The cost is one round of agent confusion per workflow that uses rollups — bounded, but consistent, and impossible to debug from the tool surface alone without reading the propose doc.
Three frame options
(A) Stand on the current decision
Rollup is read-only signal. Agent learns the 2-hop decomposition from the dot syntax and schema description.
Pros
describedescribes,neighborstraverses one hop, dot is convention.(via members)rollup keys indescribe.edge_summary(clients + routes) #89 remains intact.neighborsdoesn't grow knowledge about composed paths.Cons
(B) Surface decomposition in
describeAdd a
rollup_paths(or similar) field next toedge_summarythat machine-readably tells the agent how to traverse each composed key. Example:Pros
OVERRIDDEN_BY.DECLARES_CLIENT, etc.) gain consumers, the samerollup_pathsfield documents them uniformly.Cons
DescribeOutput. Backwards-compatible (additive), but expands the contract.rollup_pathsmakes it explicit. Worth the redundancy if it prevents the silent-zero, debatable otherwise.rollup_pathsprogrammatically or just read it as documentation. Affects schema design.(C) Make
neighborsaccept dot-notation as traversal shortcutFlip decision #11 in PR #89:
neighbors(edge_types=["DECLARES.DECLARES_CLIENT"])runs the 2-hop internally and returns the final-hop targets (or both-hop targets, design choice).Pros
describeis consumable byneighbors. No translation step.Cons
(via members)rollup keys indescribe.edge_summary(clients + routes) #89 decisions Implement PR-A2: SpEL/constant-ref routes and route MCP tools #7 (depth=1) and plan: Tier 1B (B2b + B6) plan + per-PR Cursor prompts #11 (rollup as read-only).neighborsnow knows about composed paths. The frame that ruled things out in PR propose: synthetic(via members)rollup keys indescribe.edge_summary(clients + routes) #89 stops ruling them out.["DECLARES.DECLARES_CLIENT", "DECLARES.EXPOSES"]in one call? Cross-product results? What about mixing atomic and composed keys? Each becomes a new design question.confidence,strategy,matchdoes the composed result carry? First hop? Last hop? Both?neighborsreturns one Edge per matched edge. Composed-mode returns one Edge per path, which may duplicate target nodes if multiple paths reach them.Where (Computer) lands
(B) is the natural pairing with issue #117's principle: make the tool's output self-describing instead of relying on prose-side documentation. (A) is cheap but the failure mode is durable. (C) is powerful but reopens a frame question PR #89 just locked.
But this is a real design choice and worth the same thinking-time as #117. Bundling temptation: both #117 and this one are about "the tool surface promises something the agent can't directly act on." Resist the bundle for now — different surfaces, different decisions, easier to reason about separately. Cross-link only.
Open sub-questions if (B)
rollup_paths,composed_edges,traversal_hints? Naming carries the intent.{key: human_readable_walk_string}?rollup_pathsfor all composed keys, or only the ones with non-zero counts? Latter is symmetric withedge_summary's zero-omission convention.neighborscalls fromrollup_paths, or is it documentation-grade? Affects whether the schema is fully typed or string-tolerant.Open sub-questions if (C)
edge_typescontain both atomic and composed keys in the same call?(via members)rollup keys indescribe.edge_summary(clients + routes) #89 decisions Implement PR-A2: SpEL/constant-ref routes and route MCP tools #7 and plan: Tier 1B (B2b + B6) plan + per-PR Cursor prompts #11 — explicitly relitigated, or quietly reframed?Related
(via members)rollup keys indescribe.edge_summary(clients + routes) #89 (merged via Add composed edge_summary rollups for type Symbols in describe #106, describe: override-axis edge_summary rollup for method symbols (PR-1) #110) — the rollup propose this discussion is about. Decision plan: Tier 1B (B2b + B6) plan + per-PR Cursor prompts #11 (rollup as read-only) is the locked decision that (C) would relitigate.mcp_v2.py:127–138kuzu_queries.py:611–696(member_edge_rollup_for,override_axis_rollup_for)Next step
Wait for thinking-time on issue #117 frame question first — the answer there constrains the answer here. If #117 lands strict-frame (or hybrid), (B) is the natural pairing. If #117 lands permissive, (C) becomes more defensible.