Hermes web search returns irrelevant results because the model reformulates your query before searching, often stripping critical keywords or adding context you never asked for. The fix is to route search through an API you control, where the exact query reaches the search engine without a reformulation step in front of it.
This is not a regression in any single release, and upgrading will not clear it. It is a property of letting the model choose the search string, so it behaves the same whether you are on an older build or on 0.20.x. Everything below is release-agnostic; where a detail is version-sensitive it is called out.
Root cause: query reformulation
When Hermes calls its built-in web search tool, it does not pass your text through. It writes a new query it believes is better. "Best search API for agents 2026" becomes "search API comparison" -- the year filter and the agent context are gone, and the broader query returns broader, weaker results.
This is how tool-use models are supposed to work. The LLM decides what to search for. But when your pipeline depends on specific query terms -- a version number, an error string, a year, a product SKU -- reformulation quietly destroys result quality and nothing in the output tells you it happened.
Confirm it before you fix it
Do not guess at this. Log the argument the search tool actually receives and compare it to what the user typed. Hermes prints tool calls with its verbose/debug flag, and any MCP server sitting in front of search sees the same string. If the two differ on more than whitespace, reformulation is your problem and no amount of prompt tuning on the answer will help.
Two other symptoms point the same way: results that are topically adjacent but never specific, and answers that are correct for a more general question than the one you asked.
Fix 1: bypass reformulation entirely
Call the search API yourself with the exact user query. Do not let the model choose what to search for. Feed the raw results back to Hermes as context and let it do the part it is good at -- reading and reasoning.
import os
import requests
API = "https://api.scavio.dev"
H = {
"Authorization": f"Bearer {os.environ['SCAVIO_API_KEY']}",
"Content-Type": "application/json",
}
def search_then_reason(user_query, model_url="http://localhost:11434/v1/chat/completions"):
"""Search with the exact query, then reason over the results."""
# Step 1: exact query, no LLM in the loop
r = requests.post(
f"{API}/api/v2/google",
headers=H,
json={"query": user_query},
timeout=30,
).json()
lines = []
for item in r.get("organic_results", [])[:5]:
lines.append(f"- {item['title']} ({item['link']}): {item.get('snippet', '')}")
context = "\n".join(lines)
# Step 2: hand the exact results to your Hermes backend
prompt = (
f'Search results for "{user_query}":\n{context}\n\n'
"Answer the user's question using ONLY these results. "
"Cite the URL for every claim. If the results do not answer it, say so."
)
resp = requests.post(
model_url,
json={"model": "hermes", "messages": [{"role": "user", "content": prompt}]},
timeout=120,
).json()
return resp["choices"][0]["message"]["content"]
print(search_then_reason("best search api for agents 2026"))Two details that matter. Every Scavio endpoint is a POST with a JSON body, and auth is a bearer token -- not a query string, not an x-api-key header on the REST API. And the Google endpoint returns organic_results at the top level, each item carrying title, link and snippet, alongside a credits_used counter you can log to keep an eye on spend.
Fix 2: multi-source grounding
The built-in tool reads one source. If two independent sources agree, the answer is far more likely to be right, and disagreement is itself signal worth surfacing to the model rather than hiding.
def multi_source(query):
"""Google for documents, Reddit for what practitioners actually say."""
g = requests.post(
f"{API}/api/v2/google",
headers=H, json={"query": query}, timeout=30,
).json()
rd = requests.post(
f"{API}/api/v1/reddit/search",
headers=H, json={"query": query}, timeout=30,
).json()
out = ["Google results:"]
for item in g.get("organic_results", [])[:3]:
out.append(f"- {item['title']} ({item['link']}): {item.get('snippet', '')}")
out.append("\nReddit discussions:")
# v1 endpoints wrap their payload under `data`
for post in rd.get("data", {}).get("results", [])[:3]:
out.append(
f"- r/{post['subreddit']} ({post['score']} pts, "
f"{post['num_comments']} comments): {post['title']}"
)
return "\n".join(out)
print(multi_source("hermes web search returns irrelevant results"))Note the shape difference: the Google endpoint returns the SERP payload at the top level, while the /api/v1/* platform endpoints nest theirs under data next to response_time, credits_used and credits_remaining. Write your parser against the endpoint you are actually calling rather than assuming one envelope everywhere.
Fix 3: constrain the reformulation instead of removing it
Sometimes you want the model to search -- multi-step research where the second query depends on what the first returned. In that case constrain the rewrite rather than banning it. A system prompt rule along the lines of "pass the user's exact terms through and only append, never substitute or delete" keeps year filters, error strings and version numbers intact while still letting the agent add context between steps.
This is weaker than Fix 1. It relies on instruction-following, which degrades on smaller local backends exactly where reformulation damage is worst.
Why the built-in tools are the weak link
Hermes's default web search is typically backed by a free engine such as DuckDuckGo. Smaller index, thinner snippets, and rate limiting that shows up as empty results rather than errors. Combine a weaker index with query reformulation and the two failures compound: a broadened query against a smaller index is roughly the worst case for a niche technical search.
The same pattern shows up in the bundled skills. The shipped youtube-content skill is built on youtube-transcript-api and yt-dlp, and it breaks on a rhythm familiar to anyone who has depended on free scrapers -- undeclared dependencies that fail on a clean install, transcript fetches that die behind a proxy, a Shorts filter that stopped matching. None of that is the skill author's fault; it is what happens when a tool sits directly on an unofficial surface that changes underneath it. If your agent's job depends on YouTube transcripts or metadata, that dependency belongs behind an endpoint with an owner: POST /api/v1/youtube/transcript returns the transcript as text or timed SRT in one synchronous call, and POST /api/v1/youtube/search returns structured video, shorts and channel results.
Installing it as a Hermes skill
If you would rather not hand-write the HTTP calls, Scavio publishes 50 skills on ClawHub covering all 50 platforms, and they install straight into Hermes:
hermes skills install @scavio-ai/scavio-google
hermes skills install @scavio-ai/scavio-youtube
export SCAVIO_API_KEY=sk_live_your_keyEach skill is a thin description of the same POST endpoints, so the model gets explicit tool names and parameter docs instead of inferring them. That helps with tool selection; it does not by itself stop query reformulation, so if exact-query fidelity is what you need, Fix 1 still wins.
Measuring whether it actually helped
Do not take anyone's percentage on faith, including ours. Build a fixed set of 20-30 queries drawn from your real traffic, weighted toward the ones that hurt -- version-specific, error-string, and recency-sensitive questions, because those are where reformulation does the most damage. Run each query through both paths and score three things:
- Relevance -- does the answer address the question that was asked, or a broader one?
- Verifiability -- can every claim be traced to a returned URL?
- Freshness -- does it reference current data, or something from years ago?
Score blind if you can, and keep the query set in version control so you can re-run it after any Hermes upgrade. The absolute numbers matter far less than the direction, and a fixed set is the only way to tell a real improvement from a lucky sample.
Costs
Credit cost varies by platform rather than being flat per request. Google, Amazon, Walmart, eBay, Target, Airbnb, Zillow, Redfin, SEC EDGAR, Companies House and Meta Ads are 1 credit; Threads, Yelp, Tripadvisor, Indeed, Capterra, Google Play and Home Depot are 2; G2 is 5; Kuaishou ranges from 1 to 40 depending on the endpoint. Check the docs for the endpoint you are wiring in, and read credits_used on the response if you want the number from the source.
New accounts get 50 free credits on signup, one time, no card required -- enough to run a grounding benchmark like the one above before deciding anything. Get a key.