Tool Search

When you have many MCP servers or non-core plugin tools attached to a session, their JSON schemas can consume a substantial fraction of the context window on every turn — even when only a few of them are relevant to what the user actually asked for.

Tool Searchis Hermes’ opt-in progressive-disclosure layer for that problem. When activated, MCP and plugin tools are replaced in the model-visible tools array by three bridge tools, and the model loads each specific tool’s schema on demand.

The tools that make up Hermes’ core capability set (terminal,read_file,write_file,patch,search_files,todo,memory,browser_*,web_search,web_extract,clarify,execute_code,delegate_task,session_search, and the rest of_HERMES_CORE_TOOLS) arealwaysloaded directly. Only MCP tools and non-core plugin tools are eligible for deferral.

terminal read_file write_file patch search_files todo memory browser_* web_search web_extract clarify execute_code delegate_task session_search _HERMES_CORE_TOOLS

How it works​

When Tool Search activates for a turn, the model sees three new tools in place of the deferred ones:

tool_search(query, limit?)     — search the deferred-tool catalogtool_describe(name)            — load the full schema for one tooltool_call(name, arguments)     — invoke a deferred tool

A typical interaction looks like:

Model: tool_search("create a github issue")  → { matches: [{ name: "mcp_github_create_issue", ... }, ...] }Model: tool_describe("mcp_github_create_issue")  → { parameters: { type: "object", properties: { ... } } }Model: tool_call("mcp_github_create_issue", { title: "...", body: "..." })  → { ok: true, issue_number: 42 }

When the model invokestool_call, Hermesunwraps the bridgeand dispatches the underlying tool exactly as if the model had called it directly. Pre-tool-call hooks, guardrails, approval prompts, and post-tool-call hooks all run against the real tool name — not againsttool_call. The activity feed in the CLI and gateway also unwraps so you see the underlying tool, not the bridge.

tool_call tool_call

When does it activate?​

By default Tool Search runs inautomode: it activates only when the deferrable tool schemas would consume at least 10% of the active model’s context window. Below that, the tools-array assembly is a pure pass-through and you pay no overhead.

auto

This decision is re-evaluated every time the tools array is built, so:

Configuration​

tools:  tool_search:    enabled: auto       # auto (default), on, or off    threshold_pct: 10   # percentage of context — only used in auto mode    search_default_limit: 5    max_search_limit: 20
Key Default Meaning
enabled auto autoactivates above threshold;onalways activates if there’s at least one deferrable tool;offdisables entirely.
threshold_pct 10 Percentage of context length at whichautomode kicks in. Range 0–100.
search_default_limit 5 Hits returned when the model callstool_searchwithout alimit.
max_search_limit 20 Hard upper bound the model can request vialimit. Range 1–50.

enabled auto auto on off threshold_pct 10 auto search_default_limit 5 tool_search limit max_search_limit 20 limit

You can also flip the legacy boolean shape:

tools:  tool_search: true   # equivalent to {enabled: auto}

When NOT to use it​

Tool Search trades a fixed per-turn token cost (the three bridge tool schemas, ~300 tokens) and at least one extra round trip (search → describe → call) for the savings on the deferred schemas. It’s a clear win when you have many tools and use few per turn; it’s overhead when you have few tools total.

Theautodefault handles this for you. If you setenabled: onunconditionally, expect a slight per-turn cost on small toolsets.

auto enabled: on

Trade-offs that don’t go away​

These come from the prompt-cache integrity invariant — they are inherent to any progressive-disclosure design, not specific to this implementation:

tool_describe

Implementation details​

"github" Map tool_search tool_describe tool_call

See also​

tools/tool_search.py tests/tools/test_tool_search.py openclaw-tool-search-report