Skip to content

Design: tool-def deferral coverage (detect deferral gaps, restore/tune native deferral, proxy for non-native agents) #614

Description

@AVSRPA1KR

Follow-up to the acting-layer epic (#602). This is the deferral piece called out in the epic's non-goals as a separate, evidence-gated track. Design issue first, then PR, same flow the epic used.

Scope update: the proxy fallback for non-native agents (originally part 3) has been split into its own design issue — request-path (Class C) code warrants a separate review, and it now also covers a provider-agnostic gateway (Anthropic + OpenAI), which is beyond this issue's analyzer/config scope. This issue is now scoped to detection (part 1) and native config restore/tune (part 2).

Motivation

Tool-def deferral is no longer a missing capability on the happy path: Claude Code ships tool search enabled by default — MCP tool defs are deferred and discovered on demand, and the API mechanism behind it (defer_loading + a tool search tool) is GA on the Claude API, with the prefix untouched and prompt caching preserved by design (per current docs at https://code.claude.com/docs/en/mcp and https://platform.claude.com/docs/en/agents-and-tools/tool-use/tool-search-tool).

The real gap is coverage:

  1. Deferral silently turns off. Claude Code disables tool search on Vertex AI and whenever ANTHROPIC_BASE_URL points to a non-first-party host (docs: "most proxies don't forward tool_reference blocks"). Users behind any gateway/proxy, or with a stale ENABLE_TOOL_SEARCH=false, are paying full tool-def cost on every turn with no signal that anything changed.
  2. Deferral config has tunable waste even when on. alwaysLoad: true servers that are rarely invoked, and threshold mode (auto:N) left at defaults, both leave measurable prefix cost on the table.
  3. Non-native agents have no deferral at all. Cursor, Cline, Aider and similar load every def every turn — addressed in the separate proxy design issue, not here.

None of this is routing. No model changes, no per-turn classification, no rewriting of anything the user wrote.

Captured evidence (real usage, Opus 4.x)

Measured deferred vs non-deferred on the same workloads, in separate sessions each:

  • 20-turn tool-heavy session: all defs loaded every turn $1.31 vs deferred $0.90 (~31% less, no model change).
  • Two-turn "chat → one tool lookup" flow: $0.96 non-deferred vs $0.35 deferred (~64% less).
  • Cache behavior matches the documented design: deferred defs are excluded from the prefix; a discovered tool comes back as a tool_reference block appended inline, so the cached head stays byte-stable.

Addressing the epic's evidence gate directly:

Gate Answer
Pre-turn classification accuracy Not applicable — deferral needs no classification. That concern belongs to routing, which stays out of scope here.
Cache-hit impact Positive by documented design (prefix untouched); acceptance criteria below make it measured per user, not asserted.

Scope

1. Detection (analyzer-side, always safe)

A new optimize finding family, mcp-deferral-gaps:

  • Deferral-off detection. Deterministic transcript signal: sessions carrying meaningful MCP tool-def overhead with zero ToolSearch tool invocations across the window. Config corroboration where readable: ENABLE_TOOL_SEARCH=false in settings/env, a non-first-party ANTHROPIC_BASE_URL, Vertex AI, or a Claude Code version predating the feature. Each cause gets its own message, because the fixes differ.
  • alwaysLoad hygiene. Servers or tools pinned with alwaysLoad: true (server-level in .mcp.json, or "anthropic/alwaysLoad": true in a tool's _meta) whose observed call rate doesn't justify permanent prefix residency. Reuses the per-server schema-token overhead the mcp-low-coverage detector already computes. Threshold starts conservative (flag only clear cases, e.g. under 1 call per 5 sessions) and tunes on the detector's own data.
  • Threshold tuning. Where auto mode is in use, report whether the observed def volume suggests a tighter auto:N.
  • Output: per-server estimated tokens/session in the prefix, observed call rate, estimated savings, and the detected cause. Pure reporting; ships regardless of what follows.

2. Restore/tune native deferral (config-class, journaled)

Where the agent supports deferral natively, the fix is a config edit — exactly the class of action #603/#604 already handle. New plan kinds in src/act/plans.ts:

  • defer-enable: remove a stale ENABLE_TOOL_SEARCH=false (settings.json env or shell config), or set ENABLE_TOOL_SEARCH=true explicitly for a user behind a tool_reference-capable proxy who wants deferral back. Never set true blind behind an unverified proxy — the docs are explicit that requests fail on proxies that don't support tool_reference blocks, so the plan preview must state this and the finding must distinguish "proxy verified capable" from "unknown proxy".
  • defer-alwaysload: remove or add alwaysLoad: true on specific servers in .mcp.json, per the hygiene finding. Requires Claude Code v2.1.121+; the plan checks the installed version and marks itself not-appliable below it. Note alwaysLoad: true also blocks startup on that server's connection (5s cap) — the preview says so.
  • defer-threshold: set ENABLE_TOOL_SEARCH=auto:N in settings.json env.

All through runAction(): backup, journal, undo, dry-run preview. ENABLE_TOOL_SEARCH is read at process start, so every defer-* preview states that the change takes effect on the next session. Per #605's rule, verify the exact settings surface against live docs at implementation time; the field names above were verified against current docs but the protocol evolves.

Measurement

act report (#606) integration: defer-* actions capture a baseline (tool-def tokens/session, cache-write tokens/session, cache-hit rate over trailing 14 days) and report realized deltas post-apply, with the same confidence markers. Deferral's whole pitch is cache economics, so cache-hit rate before/after is a first-class reported number — if a deferral change ever hurts hit rate in practice, the report should say so.

Non-goals

  • No per-turn model routing, no request classification, no param rewriting. (Separate future track, gated on its own evidence.)
  • No reimplementing deferral where it exists natively — native path always preferred. A proxy for agents without native deferral (or gateways that break it) is a separate design issue.
  • Nothing on by default. Detection findings are informational; both fix paths require explicit commands.
  • No cross-provider anything in this issue. (OpenAI ships its own tool search on GPT-5.4+ via the Responses API; that path — different wire protocol, different cache economics — is handled in the separate proxy/gateway design, not here.)

Acceptance

  • optimize surfaces deferral gaps with per-server evidence, detected cause, and estimated savings, with zero behavior change for users who ignore it.
  • A user whose deferral was silently off (stale env / disabling config) can see why, apply the fix where safe, and undo to a byte-identical config.
  • defer-* actions are journaled, dry-run previewable, and act undo restores byte-identical files; act report shows measured tokens/session and cache-hit deltas after the window.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions