hermes-agent/website/docs/user-guide/messaging/xchat.md

6.9 KiB

X Chat (encrypted X DMs)

X Chat is X's end-to-end encrypted direct-message system. The Hermes adapter connects your agent to a bot X account's DMs: message bodies are encrypted and decrypted locally with the official Chat XDK — X only ever routes ciphertext, and every message is signed so recipients can verify the sender.

Run hermes xchat setup for a guided walk-through, or pick X Chat in hermes gateway setup.

Prerequisites

  • An X developer account with an app configured for OAuth 2.0 user context (Developer Console). X Chat endpoints require API access on your developer plan.
  • A user access token for the bot account with scopes: dm.read, dm.write, users.read, tweet.read (add offline.access to receive a refresh token so Hermes can auto-renew the ~2-hour access token, and media.write to send encrypted file/image attachments).
  • Python 3.10+ (the chatxdk E2EE binding is lazy-installed at first use).

Setup

hermes xchat setup

The wizard:

  1. Stores the OAuth2 access token (and optional refresh token + client id) in ~/.hermes/.env.
  2. Derives the bot account's numeric user id via GET /2/users/me.
  3. Generates the E2EE identity + signing keypairs with the Chat XDK, saves the private-key blob to ~/.hermes/xchat/private_keys.b64 (mode 600), and registers the public keys with the X API.

Key registration is rate limited to a few writes per 24 hours per account. The setup is resume-safe: the key blob and registration payload are persisted before any network call, so an interrupted or rate-limited run resumes the same identity instead of minting a new one.

Check state anytime:

hermes xchat status

Environment variables

Variable Required Description
XCHAT_ACCESS_TOKEN Yes OAuth2 user access token (dm.read, dm.write, users.read, tweet.read)
XCHAT_REFRESH_TOKEN Recommended Refresh token — enables automatic access-token renewal (rotated on every refresh and re-persisted)
XCHAT_CLIENT_ID With refresh X app OAuth2 client id (required for token refresh)
XCHAT_CLIENT_SECRET Optional Only for confidential clients
XCHAT_USER_ID Auto Bot account's numeric user id (derived by setup)
XCHAT_SIGNING_KEY_VERSION Auto Registered public-key version (written by setup)
XCHAT_PRIVATE_KEYS_B64 Optional Key blob override — takes precedence over the blob file. ⚠️ This is the bot's entire E2EE identity: there is no forward secrecy in this protocol, so anyone holding the blob can decrypt all past and future messages. Keep it in ~/.hermes/.env (never commit it, never pass it on a command line)
XCHAT_ALLOWED_USERS Recommended Comma-separated numeric X user ids allowed to talk to the bot
XCHAT_ALLOW_ALL_USERS Optional true allows every sender (dev only)
XCHAT_CONVERSATION_IDS Optional Pin specific conversation ids to poll; omit to auto-discover
XCHAT_POLL_INTERVAL Optional Seconds between event polls (default 10, floor 2)
XCHAT_SEND_READ_RECEIPTS Optional true sends read receipts for processed messages (default false)
XCHAT_REQUIRE_MENTION Optional In group conversations, only respond when a wake word matches (default false)
XCHAT_MENTION_PATTERNS Optional Custom wake-word regexes (JSON list or comma-separated)
XCHAT_HOME_CHANNEL Optional Default conversation/user id for cron delivery
XCHAT_HOME_CHANNEL_NAME Optional Human label for the home channel

How it works

  • Inbound — the adapter polls each conversation's events endpoint, keeping a persistent per-conversation cursor (~/.hermes/xchat/cursors.json) of the last processed event. On the very first sight of a conversation it batch-decrypts the backlog (decrypt_events) to seed the SDK's verified conversation-key cache without replying to old messages; after that every new event (including bursts larger than one page, and messages that arrived while the gateway was down) is processed exactly once. KeyChange events (conversation-key rotations) are verified and folded into the key cache automatically; message edits are treated as new messages.
  • Outbound — replies are encrypted and signed locally (encrypt_message with the session identity), then POSTed as ciphertext. Replies to a specific message use the native threaded-reply event when the target is in the adapter's decrypted-event cache.
  • Media — both directions are fully encrypted. Inbound attachments are downloaded, decrypted with the conversation key for the event's key version, and cached locally so vision/file tools can read them. Outbound MEDIA:<path> files (images, voice notes, videos, documents) are encrypted with the latest conversation key, uploaded through the 3-step chat-media flow, and attached by media_hash_key (requires the media.write scope).
  • Senders — each new sender's public keys are fetched once and pushed into the XDK's signing-key store so their message signatures verify.
  • New conversations — standalone sends (hermes send xchat:<user-id>, cron delivery) to a bare numeric user id perform the conversation-key handshake automatically: both parties' key bindings are verified, a fresh conversation key is wrapped for each participant, and the key change is POSTed before the first message.
  • Identity — user ids are numeric X user ids; conversation ids look like 123-456 (1:1) or g123… (group).

Authorization

By default all senders are denied. Either:

  1. Set XCHAT_ALLOWED_USERS to a comma-separated list of numeric X user ids, or
  2. Use DM pairing — an unknown sender gets a pairing code; approve with hermes pairing approve xchat <CODE>.

Group conversations

Group chats (g… conversation ids) work out of the box. To keep the bot quiet unless addressed:

XCHAT_REQUIRE_MENTION=true

The default wake words are hermes / hermes agent; override with XCHAT_MENTION_PATTERNS. DMs are never gated.

Using X Chat with cron jobs

cronjob(
    action="create",
    schedule="every 1h",
    deliver="xchat",            # uses XCHAT_HOME_CHANNEL
    prompt="Check for alerts and summarise."
)

Or target a conversation directly:

hermes send xchat:<conversation-id> "Done!"

Standalone delivery opens an ephemeral E2EE session, seeds the conversation key from the conversation's backlog, encrypts, and sends — no running gateway required.

Limitations

  • Polling latency. Inbound uses REST polling (default 10s). Webhook / activity-stream delivery may come later.
  • Group creation. The bot participates in existing group conversations but does not create new groups or manage membership.
  • Access tier. X Chat API availability depends on your X developer plan; media upload additionally requires the media.write scope.