Repository navigation
Description
Summary
packages/agent/src/local_agent/tools.rs's exec_get_related_nodes tool (the local AI agent's get_related_nodes tool) supports direction: "both" (also its own default when the model omits direction), implemented by calling rel_ops::get_related_nodes twice — once with direction: "out", once with direction: "in" — and concatenating related_nodes from both calls with no deduplication:
let directions: Vec<&str> = match direction.as_str() {
"out" => vec!["out"],
"in" => vec!["in"],
"both" => vec!["out", "in"],
...
};
let mut all_nodes: Vec<Value> = Vec::new();
for dir in &directions {
let output = rel_ops::get_related_nodes(&ns, input).await?;
for node_val in &output.related_nodes { ... all_nodes.push(...) }
}This relies on an assumption that a Reverse-resolved relationship name (a schema-declared reverseName, or a built-in's fixed inverse like child_of) returns disjoint, non-overlapping node sets for direction: "out" vs direction: "in".
That assumption was only ever true by accident, as a side effect of the double-reversal bug fixed in #2791 / PR #2822: before that fix, direction: "out" on a Reverse-resolved name returned the real (correct) data, while direction: "in" almost always returned an incorrect, silent-zero result (the bug). Concatenating a real set with an empty one never double-counted.
PR #2822 fixes #2791 by making rel_ops::get_related_nodes return the SAME correct traversal for a Reverse-resolved name regardless of the requested direction (a reverse name only ever names one real traversal — see that PR's description for why this is the only design compatible with the issue's own requirement that --type invoices with no --direction flag, i.e. the CLI's default "out", must keep returning real data). As a direct, unavoidable consequence, exec_get_related_nodes's "both" fan-out now issues two calls that return the IDENTICAL node set for any reverse-resolved relationship name, so every related node is reported TWICE (and the returned count is doubled) whenever the local agent calls this tool with a reverse relationship name and direction omitted or set to "both".
This is a real, model-visible regression in tool output (duplicate rows, inflated counts), not a crash — the tool still returns a result, just a doubled one.
Why this can't be fixed at the rel_ops layer
The CLI's own default for --direction is also "out" (clap's default_value_t = Direction::Out), and by the time a direction reaches rel_ops::get_related_nodes/the daemon, there is no signal distinguishing "caller explicitly asked for out" from "caller's default filled in out" — both arrive as the literal string "out". Any fix that makes an explicit/default "out" return empty for a Reverse-resolved name would also break the CLI's plain, no-flag nodespace relationship get <id> --type invoices call, which #2791 explicitly requires to keep returning real data. So this has to be fixed on the packages/agent side, not in rel_ops.
Suggested fix
In exec_get_related_nodes's "both" branch, deduplicate all_nodes by node id before returning (or, more precisely: resolve the relationship name once, and if it resolves to a Reverse traversal, only query it once instead of fanning out to both directions — the two are otherwise guaranteed to be duplicates for that specific case).
Filed as a follow-up rather than fixed alongside #2791 because packages/agent is a separate ownership lane needing its own sign-off (per this repo's working conventions) — out of scope for a core-layer bug fix PR.
Repro
- Declare a schema relationship with a
reverseName(e.g.Invoice.billed_to -> Customer,reverseName: "invoices"), create 2+ edges. - Call the local agent's
get_related_nodestool withrelationship_type: "invoices"and nodirection(ordirection: "both"). - Observe each related node appears twice in the result.
Activity
Address review: clarify direction field doc and test comparison safety
Fix relationship get double-reversal for reverseName + --direction in (…
malibio commentedon Sep 25, 2026
Frontend: 264 files / 5286 tests passed