mirror of
https://github.com/NousResearch/hermes-agent.git
synced 2026-07-31 19:16:29 +00:00
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:
parent
b9b100da11
commit
6441b05888
2 changed files with 37 additions and 49 deletions
|
|
@ -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.
|
||||
|
||||
---
|
||||
|
||||
|
|
|
|||
|
|
@ -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():
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue