Hermes Relay(コネクター)
Hermes Relay それ自体はチャットのプラットフォームではありません。コネクターの仕組みであり、 ゲートウェイが実在するメッセージング基盤(Discord、 Telegram、Slack、WhatsApp など)を プラットフォームの資格情報を一切持たずに代理で扱えるようにします。 資格情報は *コネクター* と呼ばれる別のサービスが持ち、ボットのトークンとソケットを管理します。 ゲートウェイはコネクターに向けて外向きに 1 本の認証済み WebSocket をつなぎ、接続時に「何ができるか」を記した情報を受け取り、 そのソケットの上で正規化されたメッセージのイベント(受信)と操作(送信)をやり取りします。
主な性質は次のとおりです。
- 通信は外向きだけ。 ゲートウェイは受信用のポートを一切開きません。
受信したメッセージは、ゲートウェイ側からつないだ同じ WebSocket を通って戻ってきます。そのため relay は NAT の内側でも、公開 IP を持たないホストでも動きます。
- プラットフォームの秘密情報がゲートウェイに置かれない。 ボットのトークンはコネクターにあります。
認証が必要なプラットフォーム上のメディアの URL はコネクター側で置き直されるので、プラットフォームの 資格情報が通信路に流れることはありません。
- プラットフォームに依存しない。 ゲートウェイは、代理で扱うプラットフォームに何ができるか
(メッセージの長さの上限、markdown の方言、編集・スレッド・逐次表示への対応、そして 対応する操作の正確な一覧)を、ハードコードされた個別の処理ではなく接続時の情報から学びます。
ゲートウェイとコネクターの間の正式な取り決めは、リポジトリの docs/relay-connector-contract.md にあります。
Relay を使う場面
Relay は、ホスティングされた、あるいは共有のコネクターのサービスがプラットフォーム側を まとめて受け持つ構成のためのものです。たとえば 1 つの共有ボットが多数の利用者のエージェントを 代理する複数テナントのホスティングや、ゲートウェイのマシンにボットのトークンを置きたくない構成が該当します。 自分でボットを直接動かすなら、各プラットフォーム向けのアダプター(Telegram、 Discord など)を使ってください。
登録
自分で運用するゲートウェイは、ゲートウェイごとの秘密の値でコネクターに認証します。 hermes gateway enroll は、一度だけ使える登録用トークン (テナントの経路が用意されるときにコネクターが発行し、ゲートウェイの設定と一緒に渡されます)を その秘密の値と引き換えます。
hermes gateway enroll \
--token <enrollment-token> \
--connector-url wss://connector.example.com/relayこのコマンドは次のことを行います。
- すでにログインしている情報(
~/.hermes/auth.json)から Nous Portal のアクセストークンを
取り直します。これによって、どの Nous の組織(テナント)に属しているかが証明されます。 gateway.idp.token_url が設定されている場合は、代わりに自前の IdP が使われます(外部と切り離した構成や IdP を自前で持つ場合の経路で、Nous Portal は関与しません)。 client_id と client_secret が設定されていれば一般的な OAuth2 の クライアントクレデンシャルによる認可を行い、どちらも設定されていなければ、その URL は 周囲から自動で取得できるトークンの提供元として扱われます(単純な GET の応答本文がトークンそのものになる、 メタデータサーバー方式です。たとえば Domino の $DOMINO_API_PROXY/access-token)。 2 つの資格情報のうち片方だけを設定するとエラーになります。
- 登録用トークンとゲートウェイの id を、TLS 経由でコネクターの
/relay/enroll エンドポイントに POST します。
- コネクターがトークンを検証し(署名、一度きりであること、テナントの一致)、
ゲートウェイごとの秘密の値とテナントごとの配信キーを発行して、一度だけ返します。
- 受け取った資格情報を
~/.hermes/.envに保存します。
GATEWAY_RELAY_ID、GATEWAY_RELAY_SECRET、GATEWAY_RELAY_DELIVERY_KEY (加えて、渡された場合は GATEWAY_RELAY_URL と GATEWAY_RELAY_WAKE_URL)です。
このあとゲートウェイを再起動すると、新しい環境変数が読み込まれます。
指定できるフラグは次のとおりです。
| フラグ | 説明 |
|---|---|
--token |
一度だけ使える登録用トークン。GATEWAY_RELAY_ENROLL_TOKEN でも指定できます。 |
--connector-url |
コネクターのベース URL、または relay の URL(wss://…/relay か https://…)。GATEWAY_RELAY_URL や config.yaml の gateway.relay_url でも指定できます。 |
--gateway-id |
このゲートウェイのインスタンスを表す変わらない id(緊急停止をどの単位でかけるかに使われます)。既定は gw-<hostname> です。 |
--wake-url |
任意。ゲートウェイが休んでいる間に処理待ちのものが届いたとき、コネクターがつつくための到達可能な URL です(本文のない GET)。GATEWAY_RELAY_WAKE_URL として保存されます。設定しなくても、次に再接続したときに溜まっていたメッセージは受け取れます。 |
設定
Relay は、コネクターの relay の URL を設定した時点で有効になります。専用の オンオフの切り替えはありません。設定していない構成には何の影響もありません。
| 設定 | 場所 | 意味 |
|---|---|---|
GATEWAY_RELAY_URL |
環境変数(~/.hermes/.env) |
コネクターの relay の WebSocket の URL。設定されていること自体が relay を有効にします。 |
gateway.relay_url |
config.yaml |
上と同じものを設定ファイルに書く形(環境変数が優先されます)。 |
GATEWAY_RELAY_ID |
環境変数 | このゲートウェイのインスタンスの id(enroll が書き込みます)。 |
GATEWAY_RELAY_SECRET |
環境変数 | WebSocket の接続時の認証に使う、ゲートウェイごとの秘密の値(enroll が書き込みます)。 |
GATEWAY_RELAY_DELIVERY_KEY |
環境変数 | テナントごとの配信キー(enroll が書き込みます。今後の互換性のために保持されます)。 |
GATEWAY_RELAY_WAKE_URL / gateway.relay_wake_url |
環境変数 / config.yaml |
任意。休止中や一時停止中のゲートウェイをつつく先。 |
GATEWAY_RELAY_PLATFORMS |
環境変数 | このゲートウェイが 1 本の接続で代理するプラットフォームをカンマ区切りで並べたもの(例: discord,telegram)。ふつうは配備側や管理の仕組みが設定します。 |
GATEWAY_RELAY_BOT_IDS |
環境変数 | プラットフォームごとのボットの識別情報を表す JSON の対応表。例: {"discord": {"botId": "…"}}。GATEWAY_RELAY_PLATFORMS と対で使います。 |
gateway.idp.token_url |
config.yaml |
設定すると、登録や払い出しの認証先が Nous Portal ではなく自前の IdP になります。gateway.idp.client_id と client_secret も設定されていれば OAuth2 のクライアントクレデンシャル方式、そうでなければ周囲から自動で取得できるトークンの提供元(単純な GET でトークンそのもの、または {"access_token": …} が返る形)として扱われます。 |
対応していること
relay の接続で実際に何ができるかは、接続時のやり取りで決まります。コネクターが supported_ops の一覧を提示し、ゲートウェイはコネクターが明示した操作しか使いません (古いコネクターの場合は、従来の send/edit/typing/follow_up の組に落とします)。プラットフォームごとの できること(編集による逐次表示、スレッド、下書きの逐次表示、markdown の方言、メッセージの 長さの上限)も、接続時に受け取る情報から決まります。そのやり取りの範囲で、relay は次に対応します。
- テキストのメッセージと逐次表示 — 送信、返信、そして代理するプラットフォームが
メッセージの編集に対応している場合は編集による逐次表示ができます。 対応していない場合は、区切りごとに 1 通ずつ送る形になります。
- メディアの送受信 — 送信する画像、音声、オーディオ、動画、
文書は、コネクターにアップロードするか公開 URL で参照する形で、各プラットフォーム本来の アップロード経路を通り、説明文も一緒に届けられます。 受信した添付はエージェント向けにファイルとして手元に取り込まれます。認証が必要な プラットフォームの URL はコネクター側で置き直されるので、プラットフォームの資格情報が ゲートウェイに届くことはありません。置き直せるメディアは 25 MB までで、およそ 1 時間で期限切れになります。
- プラットフォーム本来の対話的な確認画面 — コマンド実行の承認、確認、内容を明確にするための
質問が、番号を振ったテキストの代わりにそのプラットフォーム本来の操作部品(Discord のボタン、 Telegram のインラインキーボード、Slack の Block Kit のアクション、WhatsApp のボタンやリストの メッセージ)で表示されます。ボタンを押した結果は、実際に押した利用者からの認証済みの応答として 返るので、ゲートウェイの認可の判定は、打ち込まれた返信とまったく同じように働きます。応答の 期限切れはゲートウェイ側で管理されます。
- リアクションによる受付の表し方 — 処理の状態を表すボットのリアクション
(作業中は 👀、完了時は ✅ か ❌)が relay 越しでも働きます。リアクションは できる範囲での対応で、失敗しても処理そのものは失敗しません。
- スレッドの扱い — 引き継ぎ用のスレッドの作成と、スレッド名の変更
(LLM が内容から付け直す名前も含みます)を、プラットフォームに依存しない thread_create / thread_rename の操作で行えます。人が手で付けた名前を上書きしない 仕組みも入っています。利用できるかどうかはプラットフォーム次第です(Slack のスレッドは 名前を変えられませんし、WhatsApp にはスレッドがありません)。
- 入力中の表示 — ゲートウェイは処理の間、入力中(およびその解除)をコネクター経由で
送り出します。
- チャットの情報 —
get_chat_infoの問い合わせは、コネクターが対応を示していれば
そちらに取り次がれます。
- 溜め置きの配信と起こす仕組み — ゲートウェイが休止したり切断したりしている間、
コネクターは受信したメッセージを確実に溜めておき、再接続時に順序どおりに流し直します(受け取りの確認を 伴うので、取りこぼしも重複も起きません)。起こすための URL が登録されていれば、 眠っているゲートウェイ宛ての処理待ちが届いたときにコネクターがそこをつつきます。
複数のプラットフォームの代理にも対応しています。1 つのゲートウェイが複数のプラットフォーム (Discord *と* Telegram など)を 1 本の relay の接続で代理でき、送信するメッセージには それぞれ宛先のプラットフォームの印が付きます。
困ったときは
登録が 401 で失敗する — コネクターが本人確認のトークンを検証できませんでした。 hermes auth add nous(または hermes setup)でログインし直してから、もう一度試してください。
登録が 403 で失敗する — 登録用トークンが無効か、期限切れか、 すでに使用済みか、別のテナントのものです。登録用トークンは 一度きりです。テナントの経路を用意した相手に、新しいものを発行してもらってください。
「Could not reach the connector」と出る — コネクターの URL を確認してください。wss://…/relay の 接続用 URL でも https://… のベース URL でも貼り付けられます。CLI が 自動で読み替えます。
enroll が実行を拒む — ホスティング事業者が管理する環境にいます。そこでは relay の秘密の値はホスティング側が用意します。自分で登録できるのは 自分で運用するゲートウェイだけです。
動いていた relay が無効と表示されるようになった — 接続が成立した*あとで* WebSocket が 4401 で閉じられた場合、そのゲートウェイの秘密の値が失効しています (インスタンスが撤去されたときなど)。ゲートウェイは意図的に再接続をやめ、 やり直すのではなく relay を無効として報告します。接続が一度も成立していない状態での 4401 は、 まだ準備が整っていないだけの一時的な行き違いとみなして、通常どおり再試行します。
登録したのに何も変わらない — ゲートウェイは GATEWAY_RELAY_* を 起動時に読み込みます。再起動してください(hermes gateway restart)。
ボタン・メディア・スレッドなどが黙って素のテキストに落ちる — その プラットフォームのコネクターが、接続時の supported_ops にその操作を示していません。 ゲートウェイは、コネクターが扱えない操作を送るのではなく、意図的にテキストでの動きに 落とすようになっています。