fix(skills): keep xurl SKILL self-contained; move x_search routing to gated surfaces

Follow-up on the salvaged commit: the xurl skill loads even when x_search
isn't registered (check_fn-gated on xAI credentials), so per the
cross-toolset reference rule the skill must not name it. Replaced the
skill's x_search routing block and workflow step with skill-native
wording (raw engageable posts, authenticated context, write-evidence
rule). The cross-surface comparison stays on surfaces where both are
known to exist: x-search feature docs, toolset description, tools-config
setup note, and the x_search tool schema (kept generic, no tool names).
Rewrote the routing tests to pin the placement contract, including that
the skill never names credential-gated surfaces.
This commit is contained in:
Teknium 2026-07-23 20:15:55 -07:00
parent b9b100da11
commit 6441b05888
2 changed files with 37 additions and 49 deletions

View file

@ -1,7 +1,7 @@
---
name: xurl
description: "X/Twitter via xurl CLI: raw post search, posting, DM, media."
version: 1.1.2
version: 1.1.3
author: xdevplatform + openclaw + Hermes Agent
license: MIT
platforms: [linux, macos]
@ -28,14 +28,6 @@ Use this skill for:
- raw access to any X API v2 endpoint
- multi-app / multi-account workflows
If Hermes also exposes the `x_search` tool, route by intent:
- Use `x_search` for read-only public X discovery: "what are people saying", current reactions, public claims, broad semantic search, and synthesized answers with citations.
- Use `xurl` for exact or authenticated X API work: post, reply, quote, delete, like, repost, bookmark, follow, block, mute, DM, media upload, timeline, mentions, account-specific reads, or raw v2 endpoints.
- For mixed workflows, use `x_search` to discover candidate public posts, then use `xurl read` or another exact `xurl` command only after the target post/user/action is clear.
- Never treat an `x_search` answer as evidence that an X write happened. For state-changing X actions, only the `xurl` command output or the X API response proves the action.
- Prefer `x_search` over `xurl search` when the user asks for broad public discussion and does not need X API-exact results or authenticated account context.
This skill replaces the older `xitter` skill (which wrapped a third-party Python CLI). `xurl` is maintained by the X developer platform team, supports OAuth 2.0 PKCE with auto-refresh, and covers a substantially larger API surface.
---
@ -399,13 +391,14 @@ xurl --app staging /2/users/me # one-off against staging
## Agent Workflow
1. Verify prerequisites: `xurl --help` and `xurl auth status`.
2. Before using `xurl search`, check intent. If the user needs broad public X discovery and the `x_search` tool is available, use `x_search` instead. Continue with `xurl` when the task needs an exact API read, authenticated account context, or any X write action.
2. Before using `xurl search`, check intent. Reach for it when the task needs actual post objects, authenticated account context, or leads into an X write action — it is the right surface when the user wants posts they can engage with, not just a summary of a topic.
3. **Check default app has credentials.** Parse the `auth status` output. The default app is marked with `▸`. If the default app shows `oauth2: (none)` but another app has a valid oauth2 user, tell the user to run `xurl auth default <that-app>` to fix it. This is the most common setup mistake — the user added an app with a custom name but never set it as default, so xurl keeps trying the empty `default` profile.
4. If auth is missing entirely, stop and direct the user to the "One-Time User Setup" section — do NOT attempt to register apps or pass secrets yourself.
5. Start with a cheap read (`xurl whoami`, `xurl user @handle`, `xurl search ... -n 3`) to confirm reachability.
6. Confirm the target post/user and the user's intent before any write action (post, reply, like, repost, DM, follow, block, delete).
7. Use JSON output directly — every response is already structured.
8. Never paste `~/.xurl` contents back into the conversation.
7. Only the `xurl` command output (or the raw X API response) proves that a state-changing X action happened. Never report a write as done based on any other source — search results, summaries, or prior context.
8. Use JSON output directly — every response is already structured.
9. Never paste `~/.xurl` contents back into the conversation.
---

View file

@ -1,7 +1,16 @@
"""Behavioral contract for xurl / x_search routing guidance.
These tests assert structural invariants (required topics + mutual exclusivity
of responsibility), not frozen prose snapshots.
These tests assert structural invariants (required topics + placement of the
routing guidance), not frozen prose snapshots.
Placement contract (July 2026):
- The xurl SKILL must NOT name `x_search` (or any other credential-gated
surface): the skill loads even when that tool isn't registered, so it must
describe its own search distinctively in its own terms (raw, engageable
post objects as the authenticated account).
- Cross-surface routing guidance lives where both surfaces are known to
exist together: the x_search feature docs, the toolset description, and
the tools-config setup note.
"""
from pathlib import Path
@ -21,45 +30,31 @@ def _contains_any(text: str, *needles: str) -> bool:
return any(n.lower() in lowered for n in needles)
def test_xurl_skill_routes_by_intent_not_interchangeably():
def test_xurl_skill_never_names_credential_gated_surfaces():
"""The skill must be self-contained: no cross-references to tools that
may not be registered (x_search is check_fn-gated on xAI credentials)."""
lowered = _read(XURL_SKILL).lower()
assert "x_search" not in lowered
assert "web_search" not in lowered
def test_xurl_skill_search_is_distinct_standalone():
"""Search must be described so an agent can route correctly even when
another X search surface exists raw engageable posts, authenticated."""
text = _read(XURL_SKILL)
lowered = text.lower()
# Both surfaces named so agents can choose by capability.
assert "x_search" in lowered
assert "xurl" in lowered
# x_search is discovery / read-only public research.
assert _contains_any(text, "read-only public", "public x discovery", "broad public")
# xurl owns authenticated / write / exact API work.
assert _contains_any(
text,
"authenticated",
"exact or authenticated",
"exact api",
"account actions",
"write action",
)
# Writes must not be evidenced by x_search answers.
assert _contains_any(
text,
"never treat an `x_search` answer",
"never evidence",
"proves the action",
"x api response",
)
# Prefer x_search over xurl search for broad public discovery when available.
assert "x_search" in lowered and "xurl search" in lowered
assert _contains_any(text, "prefer `x_search`", "use `x_search` instead", "route by intent")
assert _contains_any(text, "raw post")
assert _contains_any(text, "authenticated")
assert _contains_any(text, "engage", "engageable")
# Distinguish from synthesized-answer surfaces in xurl's own terms.
assert _contains_any(text, "summarized answer", "summary of a topic")
def test_xurl_agent_workflow_prefers_x_search_for_broad_discovery():
def test_xurl_skill_write_evidence_rule():
"""State-changing X actions are proven only by xurl output / X API
response never by search results or summaries."""
text = _read(XURL_SKILL)
# Workflow must preflight intent before xurl search.
assert "xurl search" in text.lower()
assert _contains_any(text, "check intent", "before using `xurl search`")
assert _contains_any(text, "broad public", "public x discovery")
assert _contains_any(text, "write action", "authenticated account", "exact api")
assert _contains_any(text, "proves that a state-changing", "proves the action")
assert _contains_any(text, "never report a write", "never treat")
def test_x_search_doc_separates_discovery_from_account_actions():