Files
hermes-agent/website/docs/user-guide/features/x-search.md
T
kshitijk4poor 2a352f96ee fix(x_search): surface degraded results + validate dates
The xAI Responses API for x_search returns 200 OK with a
synthesized fluff answer in two failure modes that callers currently
cannot distinguish from a real, citation-backed result:

1. Any narrowing filter (allowed_x_handles, excluded_x_handles,
   from_date, to_date) was active, but the X index returned no
   matching posts. The model then answers from training data.
2. The date range is malformed, inverted, or pure-future (e.g.
   from_date=2030-01-01). The API call burns quota and Grok
   responds with a generic answer.

Mitigations, both client-side:

* Validate from_date / to_date before the HTTP call:
  - Strict YYYY-MM-DD.
  - from_date <= to_date when both set.
  - from_date <= today UTC (no posts in a window that hasn't
    started). to_date in the future remains allowed so callers
    can request 'from yesterday to tomorrow'.

* Add 'degraded' + 'degraded_reason' to successful responses.
  degraded=True iff any narrowing filter was active AND both the
  top-level 'citations' array and inline 'url_citation'
  annotations came back empty. A broad query with no filters that
  returns no citations is *not* flagged degraded — that case is
  just an unsourced answer, not a filter miss.

Tests cover all four validation paths plus six degraded-flag
scenarios (each filter type, inline vs top-level citation
recovery, broad query baseline). All existing tests continue to
pass; the additions are purely additive on the success-path
response shape.

Discovered while testing the x_search toolset end-to-end:
queries scoped to @Teknium1 returned confident-sounding generic
text about Nous Research with zero citations, and from_date in
2030 produced sassy non-answers. Both are now detectable by the
caller.
2026-05-21 02:38:45 +05:30

7.5 KiB
Raw Blame History

title, description, sidebar_label, sidebar_position
title description sidebar_label sidebar_position
X (Twitter) Search Search X (Twitter) posts and threads from within the agent using xAI's built-in x_search Responses tool — works with either a SuperGrok OAuth login or an XAI_API_KEY. X (Twitter) Search 7

X (Twitter) Search

The x_search tool lets the agent search X (Twitter) posts, profiles, and threads directly. It's backed by xAI's built-in x_search tool on the Responses API at https://api.x.ai/v1/responses — Grok itself runs the search server-side and returns synthesized results with citations to the originating posts.

Use this instead of web_search when you specifically want current discussion, reactions, or claims on X. For general web pages, keep using web_search / web_extract.

Authentication

x_search registers when either xAI credential path is available:

Credential Source Setup
SuperGrok / X Premium+ OAuth (preferred) Browser login at accounts.x.ai, refreshed automatically hermes auth add xai-oauth — see xAI Grok OAuth (SuperGrok / X Premium+)
XAI_API_KEY Paid xAI API key Set in ~/.hermes/.env

Both hit the same endpoint with the same payload — the only difference is the bearer token. When both are configured, SuperGrok OAuth wins so x_search runs against your subscription quota instead of paid API spend.

The tool's check_fn runs the xAI credential resolver every time the model's tool list is rebuilt. A True return means the bearer is fetchable AND non-empty AND (if it had expired) successfully refreshed. Revoked tokens with a failed refresh hide the tool from the schema; the model simply can't see it.

Enabling the tool

Auto-enables when xAI credentials (OAuth token or XAI_API_KEY) are present. Disable explicitly via hermes tools → Search → x_search if you don't want this.

hermes tools
# → 🐦 X (Twitter) Search   (press space to toggle on)

The picker offers two credential choices:

  1. xAI Grok OAuth (SuperGrok Subscription) — opens the browser to accounts.x.ai if you're not already logged in
  2. xAI API key — prompts for XAI_API_KEY

Either choice satisfies the gating. You can pick whichever credentials you already have; the tool works identically with both. If both end up configured, OAuth is preferred at call time.

Configuration

# ~/.hermes/config.yaml
x_search:
  # xAI model used for the Responses call.
  # grok-4.20-reasoning is the recommended default; any Grok model
  # with x_search tool access works.
  model: grok-4.20-reasoning

  # Request timeout in seconds. x_search can take 60120s for
  # complex queries — the default is generous. Minimum: 30.
  timeout_seconds: 180

  # Number of automatic retries on 5xx / ReadTimeout / ConnectionError.
  # Each retry backs off (1.5x attempt seconds, capped at 5s).
  retries: 2

Tool parameters

The agent calls x_search with these arguments:

Parameter Type Description
query string (required) What to look up on X.
allowed_x_handles string array Optional list of handles to include exclusively (max 10). Leading @ is stripped.
excluded_x_handles string array Optional list of handles to exclude (max 10). Mutually exclusive with allowed_x_handles.
from_date string Optional YYYY-MM-DD start date.
to_date string Optional YYYY-MM-DD end date.
enable_image_understanding boolean Ask xAI to analyze images attached to matching posts.
enable_video_understanding boolean Ask xAI to analyze videos attached to matching posts.

The tool returns JSON with:

  • answer — synthesized text response from Grok
  • citations — citations returned by the Responses API top-level field
  • inline_citationsurl_citation annotations extracted from the message body (each with url, title, start_index, end_index)
  • degradedtrue when any narrowing filter (allowed_x_handles, excluded_x_handles, from_date, to_date) was set AND both citation channels came back empty. In that case the answer was synthesized from the model's own knowledge rather than the X index, so treat it as unsourced. false otherwise (including the "no filters set" case — a broad unsourced answer is just an answer, not a filter miss)
  • degraded_reason — short string naming which filters were active, or null when degraded is false
  • credential_source"xai-oauth" if OAuth resolved, "xai" if API key resolved
  • model, query, provider, tool, success

Date validation

from_date / to_date are validated client-side before the HTTP call:

  • Both, if provided, must parse as YYYY-MM-DD.
  • When both are set, from_date must be on or before to_date.
  • from_date must not be later than today UTC — no posts can exist in a window that hasn't started yet, so the call would be guaranteed to return zero citations.
  • to_date in the future is allowed (callers may legitimately request "from yesterday to tomorrow" to catch posts as they arrive).

Validation failures surface as a structured {"error": "..."} tool result, never as an HTTP call to xAI.

Example

Talking to the agent:

What are people on X saying about the new Grok image features? Focus on responses from @xai.

The agent will:

  1. Call x_search with query="reactions to new Grok image features", allowed_x_handles=["xai"]
  2. Get back a synthesized answer plus a list of citations linking to specific posts
  3. Reply with the answer and references

Troubleshooting

"No xAI credentials available"

The tool surfaces this when both auth paths fail. Either set XAI_API_KEY in ~/.hermes/.env or run hermes auth add xai-oauth and complete the browser login. Then restart your session so the agent re-reads the tool registry.

"x_search is not enabled for this model"

The configured x_search.model doesn't have access to the server-side x_search tool. Switch to grok-4.20-reasoning (the default) or another Grok model that supports it. Check the xAI documentation for the current list.

Tool doesn't appear in the schema

Two possible causes:

  1. Toolset not enabled. Run hermes tools and confirm 🐦 X (Twitter) Search is checked.
  2. No xAI credentials. The check_fn returns False, so the schema stays hidden. Run hermes auth status to confirm xai-oauth login state, and check that XAI_API_KEY is set (if you're using the API-key path).

degraded: true — answer with no citations

When you used allowed_x_handles, excluded_x_handles, or a date range and the response comes back with degraded: true, xAI's X index returned no matching posts but Grok still produced a synthesized answer from its own training data. The answer is unsourced — do not treat it as a real X result.

Causes worth checking:

  • Typo in the handle. Strip the @, double-check spelling, and confirm the account exists.
  • Date range too narrow or sliding past today's posts; widen and retry.
  • xAI index gap. Some active accounts intermittently fail to surface in x_search even when they post regularly. Retry after a few minutes, or use the xurl skill for direct X API reads when you need an exact handle's timeline.

See Also