feat: progressive disclosure with inputSchema stripping for idle context reduction (phase 1) - #616
Conversation
…ntation Move verbose tool descriptions (examples, field schemas, usage patterns) into an on-demand ha_get_tool_guide(topic) meta-tool. Tool descriptions are trimmed to essential summaries pointing to ha_get_tool_guide() for detailed guidance. 7 topics: automation, script, dashboard, template, entity, history, search. Total description reduction: ~20,000+ chars across 10 tools. Extracted from homeassistant-ai#579 for independent review. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Summary of ChangesHello @kingpanther13, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed! This pull request enhances the efficiency of the Home Assistant toolset for LLMs by streamlining tool descriptions. It extracts detailed usage information into a new, dedicated Highlights
Changelog
Activity
Using Gemini Code AssistThe full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips. Invoking Gemini You can request assistance from Gemini at any point by creating a comment using either
Customization To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a Limitations & Feedback Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for Github and other Google products, sign up here. You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension. Footnotes
|
There was a problem hiding this comment.
Code Review
This pull request is an excellent application of the progressive disclosure principle outlined in the repository style guide (lines 131-135). By trimming verbose tool descriptions and introducing a new ha_get_tool_guide meta-tool, this change should significantly reduce the idle context size for the language model. The implementation is clean and effective. I have one high-severity suggestion for the new tool's error handling to ensure full compliance with the repository's structured error response guidelines.
…tructured errors - Add ALL missing content from original tool descriptions to _TOOL_GUIDES (update examples, use cases, parameter details, string/conditional ops, etc.) - Strengthen tool descriptions: "REQUIRED: You MUST call ha_get_tool_guide()" to maximize LLM compliance with progressive disclosure pattern - Fix error handling in ha_get_tool_guide() to use create_error_response with ErrorCode.RESOURCE_NOT_FOUND per Gemini code review and repo style guide - Add import for ErrorCode and create_error_response from errors module - Ensure zero information loss: every detail from original descriptions exists in either the trimmed description or the corresponding tool guide Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Add required guide_response parameter to all 10 tools that reference ha_get_tool_guide(). This uses homeassistant-ai#363-level structural enforcement via parameter schema — LLMs treat required params as API contracts they cannot bypass, even when instructed to skip them. Tools updated: ha_config_set_automation, ha_config_set_dashboard, ha_config_set_script, ha_set_entity, ha_get_history, ha_get_statistics, ha_search_entities, ha_get_overview, ha_deep_search, ha_eval_template. Shared validate_guide_response() helper in util_helpers.py validates that the response is a successful JSON output from ha_get_tool_guide(). Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…ogressive-disclosure
Add _GuideInjectingClient wrapper to conftest that auto-injects a valid guide_response for tools that require it. This avoids modifying 35+ test files while ensuring all E2E tests pass with the new required parameter. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Update unit tests to include required guide_response parameter for all direct tool function calls (ha_config_set_script, ha_set_entity, ha_config_set_automation). Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
|
@kingpanther13 ,you have to test that it works correctly with LLMs after all the changes |
|
@sergeykad yeah I need to get a test environment together again to experiment...I won't be able to do that till tomorrow. However I will say that I use this method on my Hubitat MCP and it seems to work fine w/Claude.ai and Claude code. |
Resolve conflict in tools_search.py: combine imports from both branches (coerce_int_param from master + validate_guide_response from PR branch). Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Summary of research into approaches for reducing the ~35K token idle cost of 96 tool definitions. Key finding: proxy/meta-tool patterns (Tool Search, Semantic Search) add round trips but save ~93% tokens overall because the dominant cost is idle tool definitions on every turn, not the occasional schema lookup. Covers: PR homeassistant-ai#616, tool search proxy, semantic search, hybrid core+proxy, dynamic registration, defer_loading, ENABLED_TOOL_MODULES. Related: homeassistant-ai#614, homeassistant-ai#567, homeassistant-ai#605, PR homeassistant-ai#616 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Closing in favor of a more comprehensive approachAfter further research, I've determined that even fully expanding this PR to all 96 tools wouldn't sufficiently solve the core problem. The tool descriptions account for ~48% of idle token cost, but parameter schemas account for another ~41% (~18K tokens). Even with zero description text, 96 tool registrations still exceed ChatGPT's 16K tool token limit (issue #614). What we're doing instead: Implementing the Tool Search Proxy pattern — the same server-side pattern recommended by Anthropic and independently validated by Speakeasy (100x token reduction on 400-tool servers). Three meta-tools ( Key advantages over this PR:
Will be implemented incrementally — first PR moves 10 niche tools to proxy, subsequent PRs migrate the rest in batches. References:
|
Pull request was closed
Remove Field(description=...) from all parameters of the 10 Phase 1 thinned tools. Parameter names and types are preserved for typed invocation. Full parameter documentation is already available via ha_get_tool_guide() and the guide_response enforcement ensures LLMs read it before calling. Shortened guide_response descriptions from ~30 words to ~8 words. Removed get_security_documentation() concatenation from dashboard python_transform parameter. This reduces per-tool idle schema cost from ~375 tokens to ~70 tokens. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Why This PR Was ReopenedThis PR was originally closed while I explored a more aggressive approach — a server-side Tool Search Proxy (#627) that removed tools from After thorough review and discussion, the proxy approach raised legitimate MCP compliance concerns:
This PR's approach avoids all three issues. Tools stay in What's New Since ClosingThe original PR only thinned tool descriptions (Phase 1 of the progressive disclosure pattern). After analysis, I identified that inputSchema parameter descriptions are a major additional source of token bloat — especially on tools like New in this reopening:
Token impact (Phase 1):
The phased approach validates the pattern on 10 tools before expanding. Each subsequent phase is straightforward — thin descriptions, strip schemas, add guide content, add |
Resolved import conflicts in 5 tool files — adopted master's multi-line import formatting while keeping validate_guide_response imports needed by the progressive disclosure pattern. Also fixes validate_guide_response() to check topic matches expected_topic, and updates ha_get_tool_guide to set the canonical topic key in responses. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Add python_transform_security to dashboard guide (ALLOWED/FORBIDDEN operations that were previously appended via get_security_documentation()) - Add clearing_values to entity guide (empty string '' behavior for area_id, name, icon that was in stripped Field descriptions) Ensures all content stripped from inputSchema is fully preserved and served on-demand via ha_get_tool_guide(). Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
guide_response=_GR was placed outside function call parentheses during conflict resolution, causing syntax/runtime errors. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Fix 4 unit tests: add correct guide_response with matching topic (test_deep_search_error_handling, test_tools_entities, test_tools_config_scripts, test_wait_parameter) - Standardize 3 config tool docstrings to use imperative "REQUIRED: You MUST call ha_get_tool_guide()" pattern matching the other 7 thinned tools (automation, dashboard, script) - Exclude ha_get_overview from progressive disclosure: its description is already compact (554ch total) so thinning yields negligible savings while adding guide_response friction to a quick discovery tool - Enrich ha_get_tool_guide description with explicit tool names and topic keywords for better AI discoverability across all models Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
BAT (Bot Acceptance Testing) Results — 3-Way ComparisonTested with Claude Sonnet 4.5 against a live Home Assistant instance (192.168.1.150:8123). Prompt: "Search for light entities in my HA, which are on right now?" Comparison Table
Blind = no hint about the guide workflow; Guided = prompt includes "use ha_get_tool_guide first" Key Finding: Claude Discovers the Guide Workflow NaturallyIn the blind test, Claude did NOT try-and-fail. It:
The guide requirement text in the tool description is sufficient for Claude to discover and follow the workflow without any external prompting. Idle Context Savings (the real win)The per-task token comparison above doesn't show the main benefit. The real savings are in idle context per turn:
Over a 10-turn conversation, that's ~50K tokens of idle context eliminated. This is Phase 1 — fully migrated savings will be much larger. Tool-by-Tool Breakdown
Topic Validation
Additional Changes in Latest Commit
|
|
Honestly, I'm not sure if it's worth it. You pay fewer tokens upfront, but a simple check for lights ate 70k more tokens. IIRC, the whole idle cost is about 35k, so no reduction of the idle context can offset the increased call costs. |
|
Interesting. Not sure what to think. Caching might not be taken into account here. I removed caching from the BAT skill because it was adding noise to the results, but we might need it here. |
|
There might also be a tradeoff between speed and cost. We should add back time elapsed and token cache metrics to the skill. |
|
Time elapsed is definitely slightly more, but overall token cost/context usage over time is less because we're reducing the idle context even with extra trips. It'll be more apparent when more tools are transitioned over. |
|
Maybe do that with tools used less often? We save context with the least used tools and keep the most used tools in context. We need telemetry. |
|
It'll show better savings when we have more tools copied over. I realized it'll be better to exclude many of the tools that already have a short description , and to exclude some of the more important ones, so yeah focusing on less used tools is likely what I'll do. This is still very much a WIP, I'll be doing a lot more fiddling around before I mark it as ready. Also I realized some of my tests activated my other MCPs without warning me so the tokens above are likely not accurate. Will test more soon. I am also toying with the idea of doing #627 much more scaled down, like doing a proxy thing for certain categories only, for example combining all zone tools into one and doing the proxy method so their original descriptions aren't messed up. |
|
OK I did more testing using Gemini this time, This looks more promising. I will let you figure out how you feel about it. I will keep tinkering with it when I can, I'm not sure if I like this better or what I came up with in #637. BAT Results: Progressive Disclosure (PR #616) vs MasterTested with Gemini 3 Pro Preview against HA 2026.1.3 test container. All tests run without any hints about the Test 1: Idle Context (non-thinned tools only)Uses only
Takeaway: When thinned tools aren't used, PR saves ~28K prompt tokens (-19%) per session from smaller tool Test 2: All 10 Thinned Tools ExercisedExercises every thinned tool:
Takeaway: Even when all 10 thinned tools are used in a single session, PR still saves ~47K total tokens Summary
Key Takeaways
|
|
I'm going to close this one again in favor of #637, but will reopen if we need more context reduction in the future or if either of you want it. I think the extra calls and latency kinda kill it, but it's a good trick if we really need to reduce context usage further on any single tools. |
Research and conversation transcript exploring idle context reduction using FastMCP 3.0's Provider call_tool() with session state. Documents prior approaches (homeassistant-ai#616, homeassistant-ai#637), FastMCP 3.0 features investigated (Provider, Transforms, Visibility, SkillsProvider), and the first-call docs concept that forces documentation delivery on first tool use per session. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
What does this PR do?
Implements progressive disclosure to reduce idle context token usage. Tool descriptions and inputSchema parameter descriptions are stripped to minimal skeletons, with full documentation served on-demand via
ha_get_tool_guide(). A requiredguide_responseparameter enforces that LLMs read the full docs before using any thinned tool.This maintains MCP compliance — all tools remain in
tools/listwith typed parameters and native annotations. No tools are hidden, no string dispatch, no protocol-level changes.The Problem
ha-mcp registers 94+ tools. Their combined definitions (descriptions + inputSchemas) consume ~35,500 tokens — 17.8% of Claude's context, 27.8% of GPT-4o's, and well over ChatGPT's hard 16K tool token limit (#614). This wastes significant context on every turn regardless of which tools are used.
The Solution: Thin Registration + Progressive Disclosure
For each thinned tool:
guide_responserequired parameter → enforces that the LLM callsha_get_tool_guide(topic)first and pastes the outputha_get_tool_guide(topic)→ returns the full documentation on demand (restructured into JSON, same content)All tools stay registered with MCP. Typed invocation preserved. Native annotations preserved.
Note: This is a breaking change for any existing client code that calls these 9 tools directly without
guide_response. LLM-based clients will self-adapt (the error message tells them what to do), but any hardcoded integrations calling these tools will need to be updated to callha_get_tool_guide()first and pass the result.Context Flow
Per-tool idle cost: ~375 tokens → ~70 tokens (81% reduction)
Why This Approach
tools/listdestructiveHint,readOnlyHint, etc. preservedguide_responseparam prevents blind callsha_get_tool_guide()guide_responseto calls for these 9 toolsPrior Art & References
Migration Roadmap
Each phase: thin descriptions + strip inputSchema param descriptions + add
guide_response+ add guide content to_TOOL_GUIDES. No tool implementation code is modified at any phase. Tools with already-compact descriptions (likeha_get_overview) are excluded — progressive disclosure is only applied where it yields meaningful savings.Phase 1 Tools (This PR)
Excluded from thinning:
ha_get_overview— already compact (554 chars total incl. parameter descriptions), negligible savings vs. added friction for a quick discovery tool.Content Preservation Guarantee
All stripped content is fully preserved and served on-demand. Nothing is deleted — only relocated.
_TOOL_GUIDESdict, returned byha_get_tool_guide(topic)_TOOL_GUIDESdict under each topic's parameter/field documentation_TOOL_GUIDES["dashboard"]["python_transform_security"]_TOOL_GUIDES["entity"]["clearing_values"]examples,critical_guidance,troubleshootingsectionsAn LLM calling
ha_get_tool_guide("automation")receives the exact same information that was previously in the tool description and parameter Field descriptions — structured as JSON for better parsing.What Changed in Phase 1
Description thinning (already in initial PR):
_TOOL_GUIDESdict, restructured as JSON and served viaha_get_tool_guide()inputSchema stripping (new):
Field(description=...)removed from all thinned tool parametersguide_responsedescription shortened to ~8 words per toolEnforcement:
guide_responserequired parameter on all 9 thinned toolsvalidate_guide_response()validates before execution (checks non-null, valid JSON dict,success: true, topic matches expected tool topic)REQUIRED: You MUST call ha_get_tool_guide("topic") before using this tool.ha_get_tool_guidedescription explicitly lists all 9 tools it covers for AI discoverabilityguide_responsevia_GuideInjectingClientChanges
src/ha_mcp/tools/tools_utility.py—ha_get_tool_guide()+_TOOL_GUIDESwith full docs for 7 topicssrc/ha_mcp/tools/util_helpers.py—validate_guide_response()shared helpersrc/ha_mcp/tools/tools_config_automations.py— Thinned description + stripped inputSchema +guide_responsesrc/ha_mcp/tools/tools_config_dashboards.py— Thinned description + stripped inputSchema +guide_responsesrc/ha_mcp/tools/tools_config_scripts.py— Thinned description + stripped inputSchema +guide_responsesrc/ha_mcp/tools/tools_entities.py— Thinned description + stripped inputSchema +guide_responsesrc/ha_mcp/tools/tools_history.py— Thinned descriptions + stripped inputSchema +guide_response(2 tools)src/ha_mcp/tools/tools_search.py— Thinned descriptions + stripped inputSchema +guide_response(2 tools);ha_get_overviewexcluded (kept at master)README.md— Addedha_get_tool_guideto utility tools tabletests/src/e2e/conftest.py—_GuideInjectingClientauto-injectsguide_responsefor E2E teststests/src/unit/— Updated unit tests withguide_responseparameter (correct topic matching)Type of change
Testing
uv run pytest)uv run ruff check)Checklist
References