Search Once, Ask Thrice: Multi-Query tool_search With Stemming

Your agent needs a tool — maybe it needs to create a GitHub issue, send a Slack message, and search the web, all in one task. Under the old system, that was three separate tool_search calls, and if it misspelled one query (“issuse” instead of “issues”) or searched for a word that didn’t match the tool’s name exactly, it came up empty and had to try again. Every miss costs tokens and latency. Hermes v0.20.6 fixes this with a serious upgrade to tool_search (commit e455e4afd0): multi-query search, batched describe, and Snowball stemming.
The change is small in shape and big in behavior: tool_search now takes queries: string[] searched in parallel against the same catalog; results come back grouped per query, with one shared tools map holding each matched tool’s source, description, and required parameter names. tool_describe takes names: string[] and returns a map keyed by name — so one bad name no longer fails the whole call. And Snowball stemming means a query for “issues” matches a tool named create_issue, and “browsing” finds browser_exec. Let’s look at what changed and why it matters.
The old way vs. the new way
Before: one query per call, exact-ish matching, per-response fallback when a query missed:
tool_search("browser") → one list of matches
tool_search("web search") → another call, another list
tool_search("read pdf") → a third call
After: one call, several queries, grouped results:
{ "queries": ["browser snapshot", "web search cache", "read pdf"] }
Each query is searched independently against the same catalog, the limit applies per query (default 5, clamped to the configured maximum of 25), and the response groups matching tool names per query while a single shared tools map carries each tool’s source, description (400-char cap), and required parameter names — loaded once, not repeated per query. When some queries miss, a single top-level available_sources + hint block replaces the old per-response fallback.
The schema, straight from the tool definition:
queries: array of strings, “each a few keywords describing one capability (e.g.['create github issue', 'send slack message']). Searched in parallel; results come back grouped per query. A single string is accepted and treated as one query.”limit: “Maximum number of matches per query. Defaults to 5 and is clamped to the configured maximum (25 by default).”
Stemming: matching what you mean, not what you typed
The quiet hero of this change is Snowball stemming (English). The stemmer is applied identically on both the index path (when the catalog is built) and the query path, so a query for “issues” matches a tool named create_issue, “browsing” matches browser_exec, and “searches” matches web_search. You don’t need to guess the exact verb form or pluralization anymore — the engine normalizes both sides.
Implementation detail worth noting: Snowball stemmer instances keep mutable parsing state, so they aren’t safe to share across threads — and bridge dispatch can run on parallel tool-call threads. Hermes creates one stemmer per thread, lazily — a small but real correctness fix buried inside the feature.
tool_describe: batch names, fail soft
tool_describe gets the same treatment: it now takes names: string[] and returns a map keyed by name. Unknown names collect in not_found (with the refresh hint), and non-deferrable names keep their per-name spelling-check error in errors — one bad name no longer fails the whole call. Duplicates dedupe silently. So after a multi-query tool_search, the agent can describe several candidates in one call, and a typo’d name costs a note, not a retry.
What this means in practice
Three concrete wins:
- Fewer round-trips. A task that needs three capabilities gets one
tool_searchcall instead of three, then one batchedtool_describe. Fewer calls = lower latency and fewer chances for the model to lose the thread. - Cheaper discovery. The shared tools map means each matched tool’s metadata is sent once, not repeated per query — and on the wire, the tool definitions themselves dieted alongside this change (the broader schema-diet work in the same window trimmed
browser_execfrom 803 to 663 tokens/call). Discovery is one of the most token-hungry parts of a long agent run; this shaves it. - Better recall. Stemming kills the “close but not exact” miss class: “issues” →
create_issue, “browsing” →browser_exec, “searches” →web_search. The agent finds the right tool even when it phrases the query slightly off.
Why it matters for your workflows
Tool discovery is invisible plumbing — you never see it unless it fails, and when it fails, the agent either flails or picks a wrong-but-close tool. Multi-query + stemming + batched describe removes most of that failure surface. It’s the kind of upgrade that makes long autonomous runs (batch tasks, cron jobs, delegated subtasks) noticeably less brittle: the agent needs a browser tool and a search tool and a PDF reader for one task, and finds all three in one shot.
For more on the tools themselves, see our command panorama for the full surface of what tool_search can find, the browser snapshot budget for how browser sessions stay cheap, and the web search cache post for the sibling caching upgrade in the same window. The full v0.20.6 rundown is in our release notes.
One call, three queries, stemmed, grouped, deduped — the agent finds the right tool faster and cheaper, and “I couldn’t find a tool for that” gets a little rarer every day.