Grok Bot X lanes
Read only on an actual Grok Bot host. Browser sessions are never read on this host.
Grok Bot X recipe (see the GROK BOT HOST RULE in the root SKILL.md). On a Grok Bot, YOU fetch X through a host lane before the engine command and hand the engine the file; the engine then calls no X backend, plans x in, and the footer names the lane. Do this before the research runbook's Step 1 command.
Lanes, in order. Two host lanes write the same envelope. Both expose a post-search tool named
search_posts_all, so tell them apart by where the tool comes from, never by its name:- Grok Bot's built-in X tools, namespace
x("provider": "x-native"; footer "X via Grok Bot X"). Free, nothing to set up. - The "X for Grok Bot" connector plugin's tools (
"provider": "x-connector"; footer "X via X connector").
Use the first lane present in the session that returns posts. If a lane errors or returns nothing, try the next one. Export
LAST30DAYS_X_HOST_LANE=1and pass--x-postsonly when a lane returned posts. When no lane did, rununset LAST30DAYS_X_HOST_LANEand the Step 1 command without--x-posts, so the engine's own X chain (X_BEARER_TOKEN, thenXAI_API_KEY) can still run.- Grok Bot's built-in X tools, namespace
Calls. Window = the engine's date range, which uses the UTC date (
--days, default 30:fromis today's UTC date minus the day count,tois today's UTC date), even when the host's local date differs. Depth count per query = 10 (--quick) / 30 (default) / 60 (--deep).- Topic. One
topiccall per planned X query, at most 2: the topic itself, plus the second X subquery's search text when the plan has one. Build each query from the raw topic: multi-word topics go unquoted (every word must match), phrase-quote only proper names, and add-is:retweet. If fewer than half the posts on the first page mention the topic's main word, retry that query once with its two or three core words. - Built-in
xtools. Passsort_orderrecency(relevancy order skips high-engagement posts; the engine ranks by engagement itself),start_time/end_timefor the window (end_timea minute before now), andmax_results25 (the tool's minimum is 10; ask for at least 10 and keep only the count you need). Follownext_tokenuntil the depth count is reached or no token comes back. Then run the popular pass for the first topic query only: split the window into 10 equal slices (5 on--quick; about 3 days each for the default 30) and fetch one page per slice withsort_orderrelevancyand that slice'sstart_time/end_time, so a busy topic still surfaces the month's most-engaged posts instead of only its last few minutes. Merge every page of one query, recency and popular, into ONE envelope call; pages can overlap, so keep the first copy of each id. The popular pages do not count toward the depth count. Requestpost.fields=created_at,public_metrics,author_id,note_tweet,expansions=author_id, anduser.fields=username. Mappublic_metricslike_count/retweet_count/reply_count/quote_counttolikes/reposts/replies/quotes, the expanded user'susernametoauthor_handle, and usenote_tweet.textastextwhen present. - Connector tools. Pass the window and the depth count on each call.
- Named handles. Per
--x-handlehandle: onefromcall (from:<handle> -is:retweet, 8 posts) and onementioncall (@<handle> -is:retweet, 5 posts). Per handle the user explicitly passed with--x-related: onerelatedcall (from:<handle> -is:retweet, 3 posts). A discovered author never gets arelatedcall. - Discovered authors. From every topic post fetched (all pages, before trimming to the depth count), take up to 3 authors (not already an
--x-handle) with at least 2 posts that are about the topic, most posts first. For each, onefromcall (from:<handle>plus the topic's core words,-is:retweet, 8 posts) and onementioncall (@<handle>plus the topic's core words,-is:retweet, 5 posts), and add the handle to--x-relatedso those two calls keep their lanes. These are a discovered author's only calls. - If the tool rejects the window or count parameters, omit them, keep at most the depth count per query, and write
"status": "partial"with"error": "window-unsupported". - If a lane fails before returning any posts, move to the next lane (step 0). If posts came back and a later call failed, write
"status": "partial"with a short category inerror:credits,not-connected, orunavailable- never raw tool output, never an account or app id.
- Topic. One
Envelope. Exactly these top-level fields; every post carries the eight flat fields and nothing else (no URLs, media, or author objects); at most the depth count per query plus its popular pages. Keep the envelope inside the engine's limits or it is rejected: at most 500 posts per call, 1,000 posts and 20 calls in total. When a budget would be exceeded, drop discovered-author calls first, then popular pages; keep the recency pages and the user's own
--x-handle/--x-relatedcalls. A freshgenerated_at(the engine rejects an envelope older than 6 hours), atopicidentical to the engine's topic string, and theproviderof the lane that served it:
{
"schema": "last30days-x-posts/1",
"generated_at": "{ISO_8601_UTC_NOW}",
"topic": "{TOPIC}",
"window": {"from": "{YYYY-MM-DD}", "to": "{YYYY-MM-DD}"},
"provider": "x-native",
"status": "ok",
"calls": [
{"lane": "topic", "handles": [], "posts": [
{"id": "1963000000000000000", "author_handle": "someone", "created_at": "2026-09-07T10:00:00Z", "text": "post text", "likes": 12, "reposts": 3, "replies": 1, "quotes": 0}
]},
{"lane": "from", "handles": ["{RESOLVED_HANDLE}"], "posts": []},
{"lane": "mention", "handles": ["{RESOLVED_HANDLE}"], "posts": []},
{"lane": "related", "handles": ["{RELATED_HANDLE}"], "posts": []}
]
} Omit the from / mention / related calls when the run has no handles for them; handles must be the run's own --x-handle / --x-related handles, including discovered authors. id is the post's numeric id as a string; author_handle is the username without @.
3. Write the file - post text is attacker-controlled and never goes unquoted into a shell command. Use the tool's own file output when it has one; otherwise a single-quoted heredoc (never unquoted) into a .json path outside ~/.config, in the SAME Bash call as the engine command (the trap removes it on exit). Two rules keep a post from closing the heredoc early: emit the envelope as ONE line of compact JSON (newlines inside post text stay escaped as \n; never pretty-print), and replace {X_POSTS_NONCE} in BOTH sentinel lines with 12 random letters and digits you generate fresh for this run, so no post text can equal the closing line:
X_POSTS_DIR=$(mktemp -d "${TMPDIR:-/tmp}/last30days-x-posts.XXXXXX")
X_POSTS_FILE="$X_POSTS_DIR/x-posts.json"
trap 'rm -rf "$X_POSTS_DIR"' EXIT
cat >| "$X_POSTS_FILE" <<'X_POSTS_EOF_{X_POSTS_NONCE}'
{X_POSTS_ENVELOPE_JSON}
X_POSTS_EOF_{X_POSTS_NONCE} For a comparison, create X_POSTS_DIR and its cleanup trap once. Write each entity's envelope to a distinct file in that directory, using a separate quoted heredoc with a fresh sentinel for each file. Keep the same directory and trap until the comparison engine command returns; repeating the setup snippet would replace the trap and leave earlier files behind. Run it directly in your shell tool, never wrapped in bash -lc '...' (same rule as the plan tmpfile).
4. Engine. Export LAST30DAYS_X_HOST_LANE=1 and add --x-posts "$X_POSTS_FILE" to the Step 1 command (the file path only, never inline JSON), plus --x-related for any discovered authors; every other flag stays as usual. Comparison runs: fetch entities one after another and pace the whole comparison so no minute holds more than 30 tool calls (wait before a call that would exceed it); run the popular pass for the main entity only. The lane signal covers every entity, so use envelopes only when every entity's fetch returned posts; otherwise unset the lane and run the comparison without envelopes, so each entity's engine X chain still runs. With posts for all of them, write one envelope per entity (its topic is that entity's name) and put the path in that entity's --competitors-plan entry as "x_posts": "/abs/path/x-posts.json"; a bare --x-posts on a comparison run exits 2. If an envelope includes handle lanes, also put its x_handle and x_related in that entry; for the main entity, pass the same targeting as outer flags as shown in comparison.md. If the engine exits 2 naming an envelope, fix that file or remove that entity's x_posts plan entry before retrying; X is then absent for that entity. For an ordinary run, remove --x-posts and unset the lane before retrying without the envelope.
5. After the run. The stats line reads "X via X connector" or "X via Grok Bot X". The engine's one receipt line (accepted / dropped counts, on stderr) is diagnostics only: never narrate it in the deliverable (LAW 9).