Skip to content

Ship a restricted-toolset 'cmm-navigator' subagent so Claude actually uses CMM graph/LSP tools (bypasses ToolSearch deferral) #84

Description

@halindrome

Problem

Claude (in Claude Code specifically) rarely uses CMM's graph/LSP tools (search_graph, trace_path, get_code_snippet, query_graph, get_architecture) for LSP-style questions (go-to-def, find-refs, call-chain, impact). It falls back to grep + Read instead. CMM's Hybrid-LSP call resolution is therefore largely unused, because Claude only benefits from it indirectly by calling those tools.

Observed live in a working session (the agent used grep/Read for all code navigation and made zero search_graph/trace_path/get_code_snippet/query_graph calls across a long session).

Root causes (evidence)

  1. CMM tools are deferred behind ToolSearch in the main context. Claude Code's Tool Search (ENABLE_TOOL_SEARCH, default auto) defers tool schemas once they exceed ~10% of the context window. It is token-based, global, and all-or-nothing — there is no per-server / per-tool pinning. With ~11 MCP servers connected (~150+ tools) the threshold is crossed, so CMM tools are announced by name but must be ToolSearch-loaded before use. The extra hop means the always-loaded default (grep/Read) wins by inertia. (The CMM MCP server also disconnected mid-session at one point, making the tools outright unavailable.)
  2. Nothing redirects navigation off grep. CMM's own session-gate.sh allow-lists Bash, Read, Grep, Glob (they bypass the gate). The gate blocks Edit/Write/WebFetch until indexing, but never redirects navigation. So no layer discourages grep-for-symbols.
  3. Persuasion ≠ behavior change. Tool descriptions ("use INSTEAD OF grep") and the MCP instructions field are advisory. In-session evidence: the only thing that reliably changed tool selection was a hard PreToolUse block (context-mode's ctx-execute-enforcer redirecting bashctx_execute, complied with dozens of times). Persuasion lost every time; enforcement worked.

Key finding (the fix) — EMPIRICALLY TESTED

A subagent with a restricted tools: allow-list gets its CMM tools EAGERLY loaded — no ToolSearch, no deferral.

Test (this session): spawned a subagent whose definition restricts to ~20 tools including 8 granted mcp__codebase-memory-mcp__* tools. It reported:

  • All 8 granted CMM tools present with full schemas immediately, no ToolSearch needed.
  • ToolSearch was not even in its toolset; no "deferred tools" reminder.
  • ~20 tools total (curated set).

Conclusion: deferral is a main-context problem caused by aggregate tool count. A subagent restricted to a small toolset falls under the threshold and loads CMM eagerly. The restriction that scopes the subagent is exactly what un-defers its tools.

Supporting docs facts (via Claude Code docs):

  • Subagents inherit the parent's tool definitions, filtered by the agent definition's tools: allow-list; fresh context, no parent conversation. https://code.claude.com/docs/en/agent-sdk/subagents.md
  • SubagentStart hook exists and is context-only: it can inject additionalContext (plain-text advice) into the subagent, detected via agent_id/agent_type, but cannot control tools or load skills. https://code.claude.com/docs/en/agent-sdk/hooks.md
  • Only the AgentDefinition controls tools (tools:) and can preload skills (skills:).
  • Whether restricted toolsets bypass deferral is undocumented — established here empirically, so re-verify across Claude Code versions.

Proposed solution

Ship a dedicated code-navigation subagent from this repo's installer (fits the existing feat/subagent-cmm-startup-hook direction):

---
name: cmm-navigator
description: Code navigation — definitions, callers, call chains, architecture.
tools: [mcp__codebase-memory-mcp__get_architecture,
        mcp__codebase-memory-mcp__search_graph,
        mcp__codebase-memory-mcp__get_code_snippet,
        mcp__codebase-memory-mcp__trace_path,
        mcp__codebase-memory-mcp__query_graph,
        mcp__codebase-memory-mcp__search_code,
        Read]        # deliberately NO Grep/Glob — remove the fallback
skills: [codebase-memory-tracing]
---
Orient first: get_architecture -> search_graph -> get_code_snippet.
Use trace_path for callers/impact. search_code (not grep) for text search.

Rationale (each tied to evidence):

  • Restricted tools: -> CMM eagerly loaded (proven), deferral gone.
  • Omit Grep/Glob -> no lower-friction fallback; CMM is the path of least resistance (same principle as the enforcement that actually worked).
  • skills: preload -> AgentDefinition can preload a skill (hooks cannot).
  • Optional SubagentStart hook (this repo already has the plumbing) injects the orient-first decision table as additionalContext — reinforcement only.

Known gap / caveats

  • This makes a CMM-first specialist reliably available and grep-free, but does not force the main agent to delegate to it. Pair with a main-session routing nudge ("route definition/caller/call-chain questions to cmm-navigator") or invoke directly.
  • Eager-load behavior is empirically established, not documented — re-verify across Claude Code versions.
  • Don't over-gate grep generally (string literals, configs, non-indexed files are legitimate grep uses); the point is to give navigation a CMM-only lane, not to break grep.

Acceptance criteria / next steps

  • Add a cmm-navigator agent definition to the installer (restricted tools, no grep, skills preload).
  • Verify empirically that CMM tools load eagerly for it (repeat the toolset-inventory test).
  • Optional: add a SubagentStart hook injecting the orient-first additionalContext.
  • Add a main-session routing nudge (rules/CLAUDE.md snippet) to delegate navigation to cmm-navigator.
  • Document the deferral/subagent finding in the repo so it doesn't get re-derived.

Filed from a working session investigating why Claude wasn't using CMM's LSP features; all evidence above was gathered/tested in that session.

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