Feature: Serply web_search engine for Google operators, recency, and locale
Disclosure up front: I work on Serply, so I have an interest in this landing. I have tried to keep every claim below to something you can re-run yourself, and I have flagged the parts that did not work.
Problem Statement
web_search now has three engines, and all three answer the same kind of question. Tavily and You.com are AI-search indexes that return their own ranked passages; DuckDuckGo is an HTML scrape. What none of them expose is the thing a general-purpose assistant keeps needing: Google's own result set, with Google's query grammar and locale.
That shows up as three concrete gaps in src/openjarvis/tools/web_search.py today:
- Search operators do not survive. An agent that emits
site:arxiv.org retrieval augmented generationis asking for a domain-restricted search. Sent to an AI index,site:is just more tokens in the embedding; results come back from wherever. There is no way for a caller to constrain the corpus. - No recency control.
deep_researchcares a lot about whether a source is from this week or from 2021, and the tool has no parameter and no engine that can bound a query by date. - No locale. This is the one that fits OpenJarvis's framing least comfortably. A user running a personal assistant in Germany, asking in German about something local, currently gets whatever index the engine happens to serve from a US-shaped default. The tool has no notion of where the user is.
None of these are bugs in the existing engines. They are things a SERP API does and an AI-search index does not, so the gap closes by adding an engine rather than by changing one.
Proposed Solution
Add serply as a fourth selectable engine, keyed, using the existing ENGINES mechanism rather than any new abstraction.
1. The engine. A _serply_search that mirrors _youcom_search closely: httpx.get on https://api.serply.io/v1/search with an X-Api-Key header, mapping results[] of {title, link, description} into the same ### title / Source / Summary block format every engine here already produces, and returning metadata["engine"] = "serply". Failures raise _WebSearchEngineError so the existing WARNING-plus-DuckDuckGo path handles them unchanged.
No new dependency. httpx is already a base dependency, so unlike tavily-python this adds nothing to pyproject.toml.
2. auto placement that cannot change an existing install. I would slot Serply in as the last keyed option, ahead only of the keyless tier:
TAVILY_API_KEY -> tavily (unchanged) -> YOUDOTCOM_API_KEY -> youcom keyed (unchanged) -> SERPLY_API_KEY -> serply -> You.com keyless.
A Serply key therefore only ever wins over the no-key fallback. Every install that has a Tavily or You.com key today resolves exactly as it does now. Explicit engine="serply" still wins outright, as it does for every engine.
I am deliberately not proposing Serply as the zero-config default. It is keyed only, with no keyless tier, so the property #926 was built around does not apply and the You.com default should stay where it is.
3. Registration surfaces, following what #926 established:
core/credentials.py:SERPLY_API_KEYintoTOOL_CREDENTIALS["web_search"]and intoOPTIONAL_TOOL_CREDENTIALS["web_search"], since it upgrades the tool rather than gating it.security/data_boundary_audit.py: an entry in the credential egress map, and the engine set plus label branch in_describe_web_search_engine, which keeps its own copy of the engine names.spec.metadata["optional_api_keys"], leavingrequires_api_keyon Tavily for backwards compatibility.- Tests alongside the existing ones in
tests/tools/test_web_search.py,tests/core/test_credentials.py,tests/security/test_data_boundary_audit.py. - Docs:
getting-started/quickstart.md,getting-started/configuration.md,user-guide/deep-research.md.
Nothing in frontend/, desktop/ or rust/ reads the engine list, so this stays a Python-side change. Roughly the same footprint as #926.
What I measured, including what failed
Run against GET https://api.serply.io/v1/search on 2026-09-12, so you do not have to take the three gaps above on faith:
| Claim | Check | Result |
|---|---|---|
| Operators pass through | site:arxiv.org retrieval augmented generation |
HTTP 200, 5 of 5 results on arxiv.org |
| Recency filter | tbs=qdr:w |
HTTP 200, filtered result set |
| Locale targeting | X-Proxy-Location: DE |
HTTP 200, response reports device_region: "DE" |
| Device targeting | X-User-Agent: desktop |
device_type flips from the mobile default |
Two things that did not work, which I would rather say here than have you find in review:
glandhlquery parameters do nothing. They return 200 butdevice_regioncomes back empty. Locale has to go through theX-Proxy-Locationheader, and that is what I would wire up.- Answer boxes and knowledge panels are not dependable. The payload has
answers,knowledge_graphandrelated_questionskeys, but on the query I tested all three came back empty. I would map onlyresults[]and would not claim those features anywhere in the docs.
On cost, so it is on the record: Serply is a paid API with a free tier of 2,500 credits per 30 days and no credit card required. Same shape as the Tavily key that is already here, and the keyless You.com path remains the no-cost default either way.
Primitive Area
Tools
Alternatives Considered
Do nothing. Legitimate. Three engines is already more than most projects carry, and #926 landed eleven days ago, so "we just did search, come back later" is a fair answer. If locale-aware and operator-aware search is out of scope for web_search by design, say so and I will close this rather than leave it open.
Wire it as an MCP server instead. Serply has a hosted MCP endpoint, so this could be configuration rather than code. It would sit outside data_boundary_audit and outside the auto resolution, which for a project that treats the on-device boundary as a headline property seems like the wrong trade. An engine is auditable; an MCP server the user pasted in is not.
Add a generic region or time_range parameter to the tool instead. Better shape in the abstract, but only one of the current engines could honour it, so it would be a parameter that silently does nothing on two engines out of three. Worth doing later if more than one engine can support it.
Contribution
Happy to build and maintain this. I have the change working locally against main, with uv run pytest, ruff check and ruff format --check green, and can have the PR up shortly if the direction is right. I will hold off if you would rather settle the auto ordering here first.
Source: open-jarvis/OpenJarvis