feat(mcp): EdgeFilter on neighbors_v2#182
Conversation
Add typed edge_filter for CALLS-only projection (confidence, strategies, callee_declaring_role) with fail-loud schema validation, ORDER BY call site in Kuzu before pagination, and Decision 20/30 hints. Supersedes MCP-V2 no per-edge filter on neighbors (Decision 16). Co-authored-by: Cursor <cursoragent@cursor.com>
4337de8 to
bdba206
Compare
HumanBean17
left a comment
There was a problem hiding this comment.
Review (PR-2 — EdgeFilter on neighbors_v2)
Verdict: Strong PR-2 core — dedicated CALLS path, Cypher pushdown, source ordering, fail-loud mixed stored edge types, and role-trap hints match the plan. Not merge-ready until the composed-edge + edge_filter gap is fixed or fail-louded; perf test should match Decision 31.
What works well
- Scope discipline: no PR-3 surfaces (
include_unresolved,UnresolvedCallSite, pass3 deletions); ontology stays 15; Decision 16 supersession noted in PR body. - CALLS-only fast path: pushdown +
ORDER BY e.call_site_line, e.call_site_byte+ SQL pagination when noNodeFilter— matches propose §3.4.1 and the filter-before-limit invariant. - Fail-loud applicability for mixed stored types (e.g.
CALLS+OVERRIDES); strategy include/exclude xor. - Hints (Decisions 20 & 30) and AGENT-GUIDE updates (
exclude_externalnot onneighbors, HV37OTHERtrap) are clear. - Named tests + pushdown monkeypatch are good regression guards.
Blockers
1. edge_filter silently ignored when CALLS is paired with composed dot-keys
use_calls_path requires flat_labels == ["CALLS"] and not composed_keys. For edge_types=['CALLS', 'DECLARES.EXPOSES'] + edge_filter={...}:
- Applicability passes (only
CALLSis inflat_types). - Execution uses the generic untyped
[e]matcher — no pushdown and noORDER BY.
That contradicts the propose contract (“every CALLS path uses ORDER BY”) and reintroduces a silent no-op the plan explicitly reversed. Agents can combine type rollup + CALLS projection and get unfiltered, unordered CALLS while success=true.
Suggested fix (pick one): fail loud when edge_filter is populated and edge_types is not exactly ['CALLS'] (no composed keys, no second stored label); or run the dedicated CALLS path and merge composed results separately.
2. Perf test is not Decision 31
Plan: pinned method empty-filter within 1.5× pre-PR-2 median. Test uses median_sec < 3.0 * 1.5 (fixed 4.5s), not a relative baseline — won’t catch pushdown regressions on the hot path. Heavy-gate is fine; assertion should compare to an actual baseline or document manual evidence only.
Important (non-blocking)
- Conflicting role axes: only strategy include/exclude are xor’d;
callee_declaring_role+callee_declaring_roles(or include + exclude lists) can AND contradictoryWHEREclauses — consider a@model_validator. - Multi-origin
ids: each origin loads the full CALLS stream then one slice — undocumented footgun for high fan-out; worth a doc line or fail-loud cap. NodeFilter+edge_filter: pushdown + Python pagination after full load — expected, but hot methods may spike latency.- README: re-index callout mentions
edge_filter; tool table row forneighbors(~247) still omits it. MCP_HINTS_FIELD_DESCRIPTION: documents PR-3include_unresolved/dedup_callsbefore they exist — LLM clients may try non-existent params until PR-3.
Nits
- Duplicate
_client_message_processor_process_idin two test modules. - Fail-loud stderr category: Pydantic
EdgeFilter→unknown_key, applicability →edge_filter. - No ontology validation for CALLS
strategystrings (typos → empty results) — acceptable if documented.
Merge checklist
- Fail loud (or dual-path) when
edge_filteris set with composed keys or any second stored label beyond pureCALLS. - Fix or relabel perf test to match 1.5× baseline language.
- Optional: README
neighborsrow + role-axis xor validator.
Happy to re-review after (1).
Fail loud when edge_filter is paired with composed keys or extra stored labels; add role-axis xor, baseline-relative perf gate, docs, and tests. Co-authored-by: Cursor <cursoragent@cursor.com>
Review (assume green CI)Verdict: approve with reservations. Core PR-2 contract looks solid: dedicated What works well
Issues worth addressing (non-blocking unless you want polish pre-merge)
Merge recommendationMerge if we accept PR-2 limits above. Fast-follow: move fixture FQN out of |
Summary
EdgeFilteronneighbors_v2(CALLS-only, fail-loud when an attribute is not on every requested edge label):min_confidence,include_strategies/exclude_strategies(xor),callee_declaring_role/ lists / exclusions.WHERE,ORDER BY e.call_site_line, e.call_site_byte, then pagination (no pre-filter row cap).exclude_callee_declaring_roles: ['OTHER']drops known-external rows); Decision 20 OTHER-fallback and Decision 30 NodeFilter.role collision hints;MCP_HINTS_FIELD_DESCRIPTIONupdated.neighbors” (Decision 16). Ontology stays 15; no PR-3 surfaces (include_unresolved,UnresolvedCallSite, CLI stub).Test plan
.venv/bin/python -m pytest tests/test_mcp_v2.py -k "ordered_by_call_site or edge_filter" -v.venv/bin/python -m pytest tests/test_mcp_hints.py -k "role_collision or other_fallback" -vtests/test_mcp_v2.py+tests/test_mcp_hints.py+tests/test_mcp_v2_compose.py(277 passed)test_neighbors_calls_perf_empty_filter_client_message_processorwithJAVA_CODEBASE_RAG_RUN_HEAVY=1(skipped in default CI)Implements
plans/PLAN-CALLS-NOISE.mdPR-2 /propose/CALLS-NOISE-AND-RESOLUTION-PROPOSE.md§3.4.1–§3.4.2.Made with Cursor