Hermes Agent Wiki 非公式・日本語wiki

ゲートウェイをいくつも同時に動かす

目次

複数のプロファイルを、1 台の端末のなかで管理下の サービスとして動かします。プロファイルごとにボットトークンもセッションも記憶も 別々です。このページでは運用まわり、つまりまとめて起動する方法、プロファイルを またいでログを見る方法、ホストを眠らせない方法、そして launchd や systemd に ありがちな引っかかりからの復帰を扱います。

Hermes のエージェントを 1 つしか動かさないなら、このページは要りません。基本は プロファイルを見てください。また、動かす先が *別々の* 端末にあり、1 つのデスクトップアプリからまとめてつなぎたい場合は デスクトップから複数の Hermes につなぐを 見てください。

こんなときに使う

Hermes のエージェントを 2 つ以上、同時に立ち上げておきたいときの構成です。 よくある理由は次のようなものです。

  • 秘書役を 1 つの Telegram のボットに、コーディング役を別のボットに置く
  • 家族ひとりにつき 1 つ、あるいは Slack のワークスペースごとに 1 つ
  • 同じ設定の試用版と本番版
  • 調べ役 + 書き役 + cron で動くボット — それぞれ記憶とスキルを切り離して持つ

プロファイルにはもともと、プラットフォームごとの監督役の登録が 1 つずつ付きます。LaunchAgent (ai.hermes.gateway-<name>.plist)、systemd のユーザーサービス (hermes-gateway-<name>.service)、sudo hermes gateway install --system で入れたときの systemd のシステムサービス(User= によって、実行した本人のユーザーで動きます)、 Windows のタスク スケジューラのタスク、s6 や Docker のサービスのいずれかです。デスクトップアプリは プロファイルごとに自分の hermes serve の裏側を起動します。この案内はそれらを まとめて扱うための型を足すものです。

すぐ試す

# Create profiles (once)
hermes profile create coder
hermes profile create personal-bot
hermes profile create research

# Configure each
coder setup
personal-bot setup
research setup

# Install each gateway as a managed service
coder gateway install
personal-bot gateway install
research gateway install

# Start them all
coder gateway start
personal-bot gateway start
research gateway start

これだけです。独立した 3 つのエージェントがそれぞれ自分のプロセスで動き、 落ちたときとログインしたときに自動で立ち上がります。

もうひとつの形: 全プロファイルを 1 つのゲートウェイで(多重化)

ここまでの形は プロファイル 1 つにつきプロセス 1 つ です。もうひとつの形が 多重化するゲートウェイを 1 つだけ 動かすやり方です。ゲートウェイのプロセス 1 つ — どのプロファイルから起動したものでも — が唯一の受け口になり、その端末上の *すべての* プロファイル宛てのメッセージをさばきます。

既定プロファイルでの起動や停止などの操作は、そのプロセスに向けて働きます。名前付きの プロファイルは、ホストを止めずに自分のボットだけを止めたり再起動したりできます。

  • それが動いている間に hermes -p <name> gateway run を実行すると、2 つ目のプロセスを

起動する代わりに 合流 します。ホストのゲートウェイの PID と受け持つプロファイルの 一覧を表示して、終了コード 0 で終わります。<name> がまだ受け持たれていなければ、 ホストのゲートウェイに profiles/ を走査し直すよう頼み、その答えに含まれた時点で 合流します。断る(0 以外で終わる)のは、ホストのゲートウェイにどうしても受け持たせられない ときだけです。

  • hermes gateway start --all / restart --all は *ホストの多重化プロセス 1 つ* を指します。

端末上のゲートウェイのプロセスを片っ端から相手にすることはありません。自前のゲートウェイを まだ動かしているプロファイルは、止めずに報告だけし、hermes gateway migrate --multiplex の 1 行コマンドを添えます。

  • hermes gateway run --replace は、どのプロファイルが起動したかにかかわらず、

このプロファイルを受け持っている プロセスを引き継ぎます。ホストの持ち主が別の プロファイルの単独のゲートウェイ(移行していない、プロファイルごとの一式)のときは、 そのプロセスがこのプロファイルを受け持つことはないので、--replace は断って監視役の下で 再起動を繰り返す代わりに、ふつうの run とまったく同じように隣で起動します。古い Hermes は ユニットに --replace を強制する systemd の追加設定(hermes-gateway.service.d/20-replace.conf)を 書いていましたが、いまは hermes update / hermes gateway restart がそのファイルを消します。 hermes gateway run --force は、ホストのプロセスにまったく問い合わせずに、別のゲートウェイを 起動します(ホストが固まっていたり、おかしな答えを返したりするときの逃げ道です)。

  • サービスの監視役の下では、合流したときの終了コードは 0 ではなく 75 になります。

systemd・s6・launchd はどれも 75 で終わったものを少し待って再起動するので、そのユニットは 再試行を続け、ホストのプロセスがいなくなった時点で自分から役を引き継ぎます。

  • 同時に起動した 2 つのユニットが、どちらもまだホストのプロセスを見つけられないことがあります。

どちらが動くかはホストの錠が決め、負けたほうは 75 で終わって、再試行のときに合流します。 --replace はこの確認を飛ばしません(生成されるユニットはどれもこれを付けています)。 飛ばすのは --force だけです。

多重化は 既定で有効 です(gateway.multiplex_profiles の既定値は true)。ただし安全のための決まりが 1 つあります。フラグが *未設定* のときは、 既定のゲートウェイが起動時に決める「希望」として扱われ、そのまま確定にはなりません。 起動のたびに hermes gateway migrate --multiplex と 同じ事前確認を行い、まとめても安全だった場合にだけ多重化します。条件は、 プロファイルが 2 つ以上あること、自前のゲートウェイをまだ動かしている従属側が無いこと(動いているプロセスも、 入れてあるサービスも、s6 の下で実際に *up* になっているプロファイルごとの枠も含みます)、 ボットの認証情報の重複が無いこと、/p/<profile>/ の入口を持たないポートを掴む プラットフォームが無いことです。未設定は有効の意味です。妨げが何も無ければ、ゲートウェイは 多重化し、既定プロファイルの config.yaml に gateway.multiplex_profiles: true を書き込みます (コメントは残します)。これで、ファイルの内容が実際の動きと一致します。 どれかに当たると、既定プロファイルだけをさばく形で立ち上がり、ほかのプロファイルがあるホストでは そのことを はっきり 知らせます。ゲートウェイの起動時に、受け持たれないプロファイル・妨げになった 理由・直し方を書いた枠付きの警告を出し、同じ枠を hermes update の要約と hermes gateway status にも出し、ダッシュボードにはバナーを出します (/api/status が multiplex_standalone_reason を持ちます)。プロファイルが 1 つだけの環境では 警告しません。受け持つものが無いからです。断ったときは何も書き込みません。

明示した true は、移行の事前確認を飛ばします。ただし、起動するプロファイルが gateway.standalone: true で外れている場合は別です。

  • gateway.multiplex_profiles: true(移行処理が書き込む値)は、事前確認の結果に

かかわらず多重化します。自分か移行処理がそう決めた、という扱いです。

  • gateway.multiplex_profiles に いま有効な値は true の 1 つだけ で、これは自動で

書き込まれます。未設定のキーは有効として扱われ、既定プロファイルの config.yaml に明示されます。 false は 廃止 されました。ゲートウェイはその場で true に書き換え、その起動時と次の hermes update の要約で、1 回だけ枠付きの知らせを出します。黙って切り替えることはありません。 プロファイルごとのゲートウェイにするには、そのプロファイル自身の設定で gateway.standalone: true にする(一時的な互換用のしくみで、支援される構成ではありません)か、下にある境目の場合に --force を使います。

  • プロセスの環境変数の GATEWAY_MULTIPLEX_PROFILES は、明示した true と同じように、

未設定のときの判断より優先されます。

  • 名前付きの プロファイル自身の config.yaml(profiles/<name>/config.yaml)に書いた

gateway.standalone: true は 一時的な互換用のしくみ です(下を 見てください)。ホストのゲートウェイはそのプロファイルを受け持たず、そのプロファイルは --force なしで自前のゲートウェイを動かします。そのゲートウェイは、multiplex_profiles: true も 設定されていても、自分だけを受け持ちます(プロファイルごとのゲートウェイは新しく作らない を見てください)。既定プロファイルに設定した場合は警告を出して無視します。既定プロファイルが ホストのゲートウェイだからです。このキーに対応する環境変数はありません。

ほかのプロセス(hermes -p <name> gateway start、ダッシュボード、hermes gateway migrate)が、未設定のフラグがどう決まったかを推測することはありません。動いている 既定のゲートウェイの served_profiles の記録を読み、ゲートウェイが動いていないときに だけ明示されたフラグを見ます。

多重化が向く場面

  • コンテナや VPS の環境で、管理単位が N 個、ポートが N 個、PID ファイルが N 個に

なるのが負担なとき。

  • 通信量の少ないプロファイルが多数あり、1 つずつプロセスを立てるほどではないとき。
  • 起動・監視・再起動の対象を 1 つにまとめたいとき。

プロファイルごとに 1 プロセスという形は、もう暗黙のうちに *選べる* 構成ではありません。 名前付きのプロファイルの gateway install / gateway start は、--force を付けないと 断られます(プロファイルごとのゲートウェイは新しく作らない を見てください)。 多重化の穴がまだ塞がっていない間、自前のゲートウェイがどうしても要るプロファイルは、 自分の config.yaml に一時的な gateway.standalone: true を設定できます。本当の境目がまとめるのを 妨げる場合 — UNIX ユーザーごとに分かれた一式や、<default home>/profiles/ の外にある HERMES_HOME など — は、どのプロファイルも --force が道筋のままです。

フラグを固定する

フラグが未設定なら、既定のゲートウェイが起動のたびに決めます(上を参照)。固定するには、 ホストのプロセスとしてゲートウェイが動いているプロファイル(ふつうは 既定プロファイル)に フラグを設定し、そのゲートウェイを再起動します。true は起動時の事前確認が見送るような 場合でも多重化させます(false は廃止され、無視されます)。

hermes config set gateway.multiplex_profiles true
hermes gateway restart

既定プロファイルの ~/.hermes/config.yaml に書いても同じです。

gateway:
  multiplex_profiles: true

(手軽さのため、最上位に multiplex_profiles: true と書く形も受け付けます。) 多重化しているとき、既定のゲートウェイはすべてのプロファイルを数え上げ、各プロファイルで 有効になっているプラットフォームをそのプロファイル自身の認証情報で立ち上げ、 届いたメッセージを持ち主のプロファイルへ振り分けます。1 回のやり取りごとに、 振り分け先のプロファイルの設定・スキル・記憶・SOUL、そしてプロバイダーの鍵 が 解決されます。認証情報がプロファイル間で共有されることはありません。

ホストは、休止させていない従属側のプロファイルを自動で受け持ちます。休止させたプロファイルを つなぎ直すには、そのプロファイルで gateway start を使います。

ホストを止めずに 1 つのプロファイルだけ止める

ホストの多重化プロセスが受け持っている名前付きのプロファイルでは、次のようにします。

hermes -p coder gateway stop     # park coder; other profiles keep running
hermes -p coder gateway start    # unpark coder and serve it again
hermes -p coder gateway restart  # reconnect coder with its current configuration

stop はプロファイルのホームに gateway.parked を書き込み、それからホストに、そのプロファイルの アダプターを止めて、以降の周期でそのプロファイルの cron ジョブを外すよう頼みます。この目印は ホストを再起動しても残ります。中身は見ないので、空のファイルで足ります。環境を用意する段階で <profiles-root>/coder/gateway.parked を先に作っておけば、入れたプロファイルをつながない 状態のまま置いておけます。休止させても、プロファイルそのもの、そのセッション、予定したジョブは 消えません。

start は目印を消し、それから動いているホストにそのプロファイルを受け持つよう頼みます。 ホストが動いていなければ、目印を消してからふつうの起動の手順に進みます。求められたら、 既定プロファイルからホストを起動してください。restart は休止の目印を書かずに、そのプロファイルを いったん外してから受け持ち直し、設定を読み直します。 休止中のプロファイルで、プロファイルごとのゲートウェイが動いていなければ、restart は start と同じように動きます。目印を消し、そのプロファイルを動いたまま受け持たせます(目印と並べて --force で起動したゲートウェイは、自分自身の再起動をそのまま行います)。どの操作も、cron の周期ですでに 送り出された作業を打ち切ることはありません。

ホストは 30 秒ごとにも走査し直します。手で目印を足すとそのプロファイルは外れ、手で消すと また受け持たれる対象に戻ります。制御用のソケットが頼みを確認しなかったときは CLI がそう伝え、 次の走査で目印の状態が反映されます。アダプターの片付けや接続には、さらに時間がかかることが あります。目印がある間、hermes -p coder gateway status は parked (hermes -p coder gateway start) と表示します。

起動したプロファイルは外せません。既定プロファイルの目印は警告を出して無視され、その起動・停止 などの操作と --all の付く形は、ホスト全体に働くままです。別に動いている --force の ゲートウェイは、そのプロセスの起動・停止をそれ自身で持ち続けます。

受け持たれているプロファイルでは、ダッシュボードとデスクトップアプリの Stop / Start ボタンも同じ働きをします。Stop は休止させ(/api/gateway/stop?profile=coder が hermes -p coder gateway stop を立ち上げます)、Start はホストのゲートウェイが動いている間に 休止を解き、/api/status は parked_profiles を並べます。休止して *いない* 名前付きの プロファイルで Start を押すと、いまも 409 が返ります。自前のゲートウェイが要ることになるからです。

休止と gateway.standalone: true の違い

この 2 つが同じプロファイルに同時に当てはまることはなく、一方がもう一方を意味することも ありません。

プロファイル ホストが受け持つか -p X gateway stop -p X gateway start
受け持たれている(ふつうの場合) はい 休止させる(目印 + unserve-profile) すでに受け持たれている
休止中(gateway.parked がある) 休止を解くまでいいえ すでに休止中 休止を解く(目印を消す + serve-profile)
単独(gateway.standalone: true) 決して受け持たない 自前の ゲートウェイのプロセスを止める。目印は書かない 自前のゲートウェイを起動する

gateway.standalone が優先されます。ホストは単独のプロファイルに gateway.parked を書くことはなく、 休止していてもいなくても受け持つことはないので、そのプロファイルでの stop と start は プロセスごとの意味のままです。休止は、多重化の考え方に沿って 1 つのプロファイルだけを 止めるやり方です。一時的なしくみ が残されていた理由の 「プロファイルごとの停止・再起動」の穴は、これで塞がります。

プロファイルごとのゲートウェイは新しく作らない

既定では、ホストのゲートウェイ 1 つがすべてのプロファイルを受け持ち、名前付きのプロファイルが 自分専用のゲートウェイを持つことはありません。下にある外し方を使わない限り、 hermes -p coder gateway install(start、run、 hermes -p coder setup のサービスの手順も同じ)は、いまホストのゲートウェイが動いているかどうかに かかわらず、終了コード 78 で断られます。

❌ Profile 'coder' does not get a gateway of its own.
  Exactly one gateway per host is the inbound process for every
  profile. Starting a separate gateway for this profile would
  double-bind its platforms (two pollers on one bot token, port
  conflicts).

  Install or start the host gateway from the default profile; it serves this one too:

    hermes gateway install

  Or fold an existing per-profile fleet onto one host gateway:

    hermes gateway migrate --multiplex

  A separate per-profile gateway (for a fleet split across UNIX users or a
  HERMES_HOME outside profiles/) needs --force:  hermes -p coder gateway install --force

  Temporary compatibility path while multiplexing gaps are closed: set
  gateway.standalone: true in profiles/coder/config.yaml,
  then wait for the host gateway to rescan (<=30s) or send its rescan-profiles control verb.
  (gateway.standalone is a temporary compatibility shim while multiplexing gaps are fixed;
  it will be removed once they are — plan to fold this profile with `hermes gateway migrate --multiplex`.)

ホストのゲートウェイがすでに動いていてそのプロファイルを受け持っているときは、1 行目が The host gateway already serves profile 'coder'. になり、持ち主の PID と受け持つプロファイルの 一覧が添えられ、案内されるコマンドは hermes -p default gateway restart になります。 ダッシュボードで名前付きのプロファイルの Start ボタンを押しても、同じように断られます。

一時的なしくみ: gateway.standalone: true

プロファイル自身の config.yaml に gateway.standalone: true を設定します。

# profiles/coder/config.yaml
gateway:
  standalone: true

するとホストのゲートウェイはそのプロファイルを受け持たなくなり、起動時のログに profile 'coder' is standalone (gateway.standalone: true); not served by this gateway と残ります。hermes -p coder gateway install|start|run は --force なしで 動きます。動いているホストがまだそのプロファイルを受け持ち一覧に載せているときは、走査し直すまで コマンドは断られます。次の走査(ふだんは長くて 30 秒)を待つか、ホストのゲートウェイに rescan-profiles の制御コマンドを送ってください。ホストの再起動は要りません。

キーを消すと、そのプロファイルはまたホストに受け持たれる対象に戻ります。そのプロファイル自身の ゲートウェイが動いている間は、ホストは足すのを見送り、先に止める必要があるとログに書きます。 そのゲートウェイを止めれば、次の走査でホストがそのプロファイルを受け持ちます。

単独のプロファイルのアダプター、cron、webhook の入口、Kanban の通知は、そのプロファイル自身の ゲートウェイが動いている間だけ働き、ホストの多重化プロセスや hermes serve の下では働きません。 webhook を送る側は、単独のゲートウェイ自身の待ち受け先に向けてください。ホストの /p/<profile>/ の入口は、もうそのプロファイルを受け持ちません。その待ち受け先のポートは、 プロファイル自身の .env か config.yaml から決まります。そのため、単独のゲートウェイとホストの ゲートウェイの両方で API サーバーか webhook の入口を有効にする場合は、プロファイルに専用の API_SERVER_PORT / WEBHOOK_PORT を割り当ててください。どちらも既定のままだと、2 つのゲートウェイが 同じポートを掴もうとします。cron の送り先の選択肢には、 単独のプロファイルもいまだに bot-chat:<name> として並びますが、ホストはそこへ届けられません。

hermes -p coder gateway status は、そのプロファイル自身のゲートウェイの状態の前に standalone by config (gateway.standalone: true) と表示し、hermes gateway status(既定)は受け持ち一覧のあとに standalone by config: coder と並べます。hermes gateway migrate --multiplex はそのプロファイルに 手を付けず、Standalone by config (gateway.standalone: true), left alone と表示します。WhatsApp のブリッジと中継は、ほかの単独のゲートウェイと同じく、 そのプロファイル自身のゲートウェイで動きます。

--force はこのしくみの道筋ではありません。これは、断るときの案内に書かれた 2 つの境目の 場合(UNIX ユーザーごとに分かれた一式と、profiles/ の外にある HERMES_HOME)の逃げ道のままです。 これを付けるとプロファイルごとの本物のサービスが入り、そのサービス(ExecStart には --force が 付きません)は、それ以降ふつうに起動し続けます。

多重化を有効にすると変わること

多重化すると、いくつかの挙動が変わります。gateway.standalone: true で外れているプロファイルや、 境目のあるホストで --force を付けて別のゲートウェイを動かしているプロファイルには、どれも 当てはまりません。

1. 従属側のプロファイルは自前のゲートウェイを起動してはいけない

多重化が動いている間、名前付きのプロファイルの gateway run はそれに合流し、 gateway install は別のプロセスを作るのを断ります(終了コード 78)。CLI はサービス管理の しくみに触れる前に断るので、systemd の失敗したままのユニットや launchd の再起動の輪が 生まれません。ホストのなかの従属側を管理するには、上にあるプロファイルごとの stop、start、 restart を使ってください。既定プロファイルでの hermes gateway stop は、いまも受け持っているすべてのプロファイルをまとめて止めます。 ダッシュボードとデスクトップアプリも CLI に従います。受け持たれているプロファイルでは、 「Stop」は休止させ、「Start」は休止を解きます(上を参照)。休止していない名前付きの プロファイルでの「Start」は、同じ説明を添えて 409 を返します(System のページにその場の 知らせとして表示されます)。「Restart」は失敗するしかない -p coder gateway restart を立ち上げるのではなく、多重化のプロセス(実際にその プロファイルを受け持っているプロセス)を再起動します。この再起動はその端末のすべての ボットをつなぎ直すので、どちらのアプリも先に *"Restart the shared gateway? All bots on this device reconnect: default, coder, research"* と確認し(並ぶのは動いているゲートウェイの served_profiles です)、 終わると *"Shared gateway restarted (3 bots)"* と知らせます。単独のプロファイルは これまでどおりの再起動のままです。/api/status?profile=coder も同じ一覧を gateway_shared_with として持ちます(単独のゲートウェイなら null)。 「受け持っている」かどうかは動いている ゲートウェイ自身の記録(既定のホームの gateway_state.json にある served_profiles)から読むので、多重化を既定プロファイルの環境変数 GATEWAY_MULTIPLEX_PROFILES だけで有効にした場合や、ゲートウェイの起動後に プロファイルが足された場合でも正しいままです。

設定の流れも同じ決まりに従います。hermes -p coder setup gateway、hermes -p coder setup、 hermes -p coder gateway setup、hermes -p coder import はそのプロファイルのボットを設定しますが、 受け持たれているプロファイルでは「ゲートウェイを裏で動くサービスとして入れる」手順を飛ばし、 動かないまま置かれるだけの余計なユニットや plist を登録する代わりに *「Profile 'coder' is already served by the default multiplexer」* と表示します。ボットトークンを 足せば、動いている多重化がそれを拾います。

多重化は唯一の受け口です。2 つめのプロファイルのゲートウェイが立つと、その プロファイルのプラットフォームを二重に掴んでしまいます。あえて別のプロセスにしたい プロファイルは、gateway.standalone: true で外れます(プロファイルごとのゲートウェイは新しく作らない を見てください)。--force(run、start、install、restart が受け付けます)は、 境目がまとめるのを妨げる場合にだけ使ってください。 そのため、このページの前のほうにあるプロファイル横断の起動停止をまとめる スクリプトは、多重化のときは 使いません。ホストか、その名前付きのプロファイルを直接管理します。

2. HTTP で受けるプラットフォームは /p/<profile>/ の接頭辞で届く

従属側のプロファイル宛ての HTTP で受ける通信は、2 つめのポート ではなく、 既定プロファイルの 1 つの 待ち受けにプロファイルの接頭辞を付けて届きます。

# default profile
POST http://host:8644/webhooks/<route>
# the "coder" profile, same listener
POST http://host:8644/p/coder/webhooks/<route>

接頭辞に知らないプロファイルや未設定のプロファイルを書くと 404 が返ります。 共有の待ち受けは既定プロファイルの api_server のポート(API サーバーが有効で なければ webhook のポート)で、プロファイルの接頭辞付きの宛先を 3 種類受け持ちます。

  • api_server と webhook は映し出されるだけで、二重には立ちません。

/p/coder/v1/... と /p/coder/webhooks/<route> には、既定プロファイル自身の アダプタが coder の範囲で応えます。そのため従属側は api_server や webhook を 自分で有効にしては いけません(ダッシュボードは 409 で断ります。従属側の .env にある API_SERVER_KEY や WEBHOOK_ENABLED は、待ち受けを立てずに 認証情報だけを結び付けます)。

  • ほかの受信ポート型のプラットフォームは、すべて共有の待ち受けの形で動きます。

Twilio SMS、LINE、Teams、BlueBubbles、Microsoft Graph、WhatsApp Cloud、WeCom の 呼び返し、飛書の Webhook の形を設定した従属側は、ポートを持たずに作られた 自分の アダプタを持ちます。既定の待ち受けは /p/<profile>/<the adapter's usual path> をそのアダプタへ渡します。 多重化のもとでの受信ポート型のプラットフォームを見てください。

  • WhatsApp(ブリッジ)は、ペアリングしたプロファイルごとに動きます。 従属側はそれぞれ

hermes -p work whatsapp でペアリングしてください。プロファイルごとに自分のセッションとブリッジのポートを使います。 ペアリングしていないプロファイルは whatsapp_unpaired として見送られ、ペアリングの手順が案内されます。 WhatsApp を複数のプロファイルで使う設定を見てください。

  • Relay は、これまでどおり既定プロファイルが持つ共有の受け口です。 Relay は既定プロファイルで

有効にして設定し、受け取った内容は profile_routes でプロファイルへ振り分けてください。従属側だけに Relay の設定がある場合は、受け持つ者がいないと報告されます。 gateway.standalone: true で外れたプロファイルは、ほかの単独のゲートウェイと同じく、 自分のゲートウェイで自前の中継を動かします。

認証は URL に書かれたプロファイルに従います。接頭辞のない宛先は、これまでどおり 既定の待ち受けの認証情報を使います。

  • /p/coder/... への API サーバーの要求では、~/.hermes/profiles/coder/.env の

API_SERVER_KEY を使う必要があります。既定の待ち受けの鍵は拒否されます。 多重化のもとでは、この鍵は接頭辞の認証にだけ効きます。従属側で 2 つめの api_server の待ち受けが立つわけではないので、従属側の config.yaml に platforms.api_server.enabled: false を書いて押さえておく必要はありません。

  • coder 宛ての Webhook の経路は、既定プロファイルの config.yaml で、経路ごとの

secret の隣に profile: coder と書く必要があります。その合言葉は /p/coder/webhooks/<route> でだけ通り、ほかのプロファイルの接頭辞では拒否されます。

  • profile の書かれていない Webhook の経路は既定プロファイルの経路のままで、

プロファイル名付きの接頭辞からは届きません。動的な登録も同じように結び付きます。 hermes webhook subscribe <name> --route-profile coder を実行すると、既定のゲートウェイの webhook_subscriptions.json に profile: coder が書き込まれ、/p/coder/webhooks/<name> の URL が表示されます(結び付きは hermes webhook ls で確かめられます)。全体オプションの -p coder ではなく --route-profile を使ってください。-p を使うと、登録は coder 自身の 登録ファイルに書き込まれますが、既定のゲートウェイの Webhook アダプタはそのファイルを読みません。

  • 送り返しも同じ結び付きに従います。profile: coder の経路の返信(または

deliver_only のメッセージ)は、deliver のプラットフォームについて coder の アダプタから出ていき、deliver_extra.chat_id が未設定なら coder の 定位置の チャンネルへ落ち、github_comment での送付は profiles/coder/.env の GH_TOKEN / GITHUB_TOKEN で gh を走らせます。coder にそのプラットフォームの アダプタが無ければ、ほかのプロファイルのボットとして投稿するのではなく、送付が 失敗します(502)。同じように、既定の経路が従属側でだけ有効なプラットフォームを 借りることもありません。

  • /p/coder/api/platforms/<platform>/events への呼び返しは coder のアダプタが検証し、

さばきます。coder にアダプタが無ければ、その呼び返しは 503 になります。

プロファイル名付きの API の要求は、宛先のプロファイルに API_SERVER_KEY が無ければ、通さない側に倒れます。安全にかかわる設定の誤りは、 これまでどおり致命的です。たとえば、方針が open の プラットフォームに GATEWAY_ALLOW_ALL_USERS もプラットフォーム固有の全員許可の 指定も無い場合は、危うい設定のプロファイルを黙って落とすのではなく、ゲートウェイの 起動そのものを中止します。

多重化のもとでの受信ポート型のプラットフォーム

単独の hermes -p coder gateway run は、coder の Twilio、LINE、Teams などの Webhook の サーバーをそれぞれ自分のポートで立てます。多重化のもとでも、それらのアダプタは coder のものです。profiles/coder/.env の同じ認証情報、同じ config.yaml を使い、 返信も coder のチャンネルから出ますが、ポートは 1 つも 掴みません。 既定プロファイルの共有の待ち受けが /p/coder/<path> をそれらへ渡します。 <path> は、そのアダプタが単独で受け持つときとまったく同じ宛先です。要求は coder の アダプタが coder の 合言葉(Twilio の認証トークン、LINE のチャンネルの シークレット、Teams のアプリの認証情報、BlueBubbles のパスワードなど)で検証し、 coder の実行時の範囲で動きます。既定プロファイル自身の /path には影響がなく、 その宛先のアダプタを持たないプロファイルには 404 が返ります。ほかのプロファイルの ボットに届くことはありません。

プラットフォーム 共有の待ち受けでの従属側プロファイルの呼び返し URL 名指しされたプロファイルの何で検証するか
Twilio SMS(sms) https://<host>/p/<profile>/webhooks/twilio TWILIO_AUTH_TOKEN の署名(SMS_WEBHOOK_URL はこの URL にする)
LINE(line) https://<host>/p/<profile>/line/webhook(メディア: /p/<profile>/line/media/...) LINE_CHANNEL_SECRET
Microsoft Teams(teams) https://<host>/p/<profile>/api/messages TEAMS_CLIENT_ID の Bot Framework のトークン
BlueBubbles(bluebubbles) http://<host>/p/<profile>/bluebubbles-webhook(サーバーへ自動で登録される) BLUEBUBBLES_PASSWORD
Microsoft Graph(msgraph_webhook) https://<host>/p/<profile>/msgraph/webhook extra.client_state
WhatsApp Cloud(whatsapp_cloud) https://<host>/p/<profile>/whatsapp/webhook WHATSAPP_CLOUD_APP_SECRET / 検証トークン
WeCom の呼び返し(wecom_callback) https://<host>/p/<profile>/wecom/callback アプリの呼び返しのトークン / AES の鍵
飛書の Webhook の形(feishu) https://<host>/p/<profile>/feishu/webhook FEISHU_VERIFICATION_TOKEN / FEISHU_ENCRYPT_KEY

<host> は、既定プロファイルの待ち受けの前に置いた公開のホスト名(トンネルや リバースプロキシ)です。プロファイルの設定で webhook_path を独自に書くと、 /p/<profile> のあとの宛先もそれに合わせて変わります。ゲートウェイは起動時に 正確な URL を記録します。

[sms] profile 'coder' is served on the default profile's shared listener:
http://127.0.0.1:8642/p/coder/webhooks/twilio (point the vendor's callback URL at this path ...)

状態を見せるどの画面も同じ URL を繰り返すので、提供元の管理画面に何を貼ればよいかが わかります。

$ hermes -p coder gateway status
✓ Gateway is running via the default-profile multiplexer
  Manage it from the default profile: hermes gateway status

Inbound callback URLs on the shared listener:
  line: http://127.0.0.1:8642/p/coder/line/webhook
  sms: http://127.0.0.1:8642/p/coder/webhooks/twilio

既定プロファイルでの hermes gateway status と hermes status は、受け持っている プロファイルごとに同じ URL を並べます。ダッシュボードの Channels のページと デスクトップの Messaging のページも、そのプロファイルを見ているときは各プラット フォームの ingress_url として表示します。既定プロファイル自身の api_server と webhook も、受け持たれているプロファイルについては同じように報告されます。 ingress_url が http://127.0.0.1:8642/p/coder/v1(後者なら .../p/coder/webhooks/<route>)の 接続済み として出ます。そのプロファイルはそれらのアダプタを自分では持たず、 /p/coder/ の接頭辞で応えているのは既定の待ち受けだからです。従属側の .env に プロファイルごとの SMS_WEBHOOK_PORT、LINE_PORT、TEAMS_PORT などを書いても、 多重化のもとでは無視されます(何も掴まないからです)。そのプロファイルが自前の ゲートウェイを単独で動かした時点で、また効くようになります。

3. 認証情報ごとのプラットフォームは、プロファイルごとにトークンが要る

問い合わせ型・接続型のプラットフォーム(Telegram、Discord、Slack、Matrix、Signal など)は多重化しても問題なく動きますが、それを有効にするプロファイルはそれぞれ 自分の ボットトークンを用意しなければなりません。同じトークンを 2 つの プロファイルが同時に使うことはできないからです。2 つのプロファイルが同じ (platform, token) を設定すると、ゲートウェイは両方のプロファイル名を挙げた エラーを記録し、重複したほう のアダプタを止め置きます(実行中の状態では fatal / duplicate_credential と表示されます)。先に名乗ったほうとほかの プロファイルは動き続け、ゲートウェイ自体は終了しません。既定プロファイルの アダプタが先につないで認証情報を取るので、止め置かれるのは必ず従属側です (トークンのぶつかりを防ぐしくみも見てください。決まりは 変わっておらず、1 つのプロセスのなかで効くようになっただけです)。

4. セッションの鍵はプロファイルごとに名前空間が分かれる

各プロファイルのセッションは agent:<profile>:… という名前空間の下に置かれるので、 同じプラットフォームの同じチャットにいる 2 つのプロファイルが、共有の保管庫で ぶつかることはありません。既定 プロファイルは従来の agent:main:… を 1 バイトも 変えずに保つので、既存の既定プロファイルのセッションには影響がありません。移行も 不要で、行き場を失う履歴もありません。鍵を読み戻すゲートウェイの経路はすべて —— 再起動後の委任の完了通知、停止のお知らせ、利用者ごとのスレッドで兄弟の実行を /stop すること、/undo、QQ の承認ボタン —— agent:<profile>:… の形も受け付けるので、 従属側のプロファイルも既定と同じように振る舞います。既定の名前空間とぶつかる 唯一のプロファイル名、つまり文字どおり main という名前のプロファイルは、agent:main~:… を 鍵にするので、自分のセッションと自分の profiles/main/state.db を保ちます。

各プロファイルの行は それぞれの state.db に入ります。名前付きプロファイルは profiles/<name>/state.db、既定プロファイルは起動時のホームの下です。ほかの プロファイルに振り分けられたやり取りや裏で動く定期処理のなかで書き込みが起きた ときも同じです。デスクトップや TUI の裏側が持つ保管庫も、起動したホームに 固定されます。Bot Chat の脇役のエージェント(prompt.background)は、親の会話の 隣に保存されます。

5. PID と錠は 1 つ、状態の見え方も 1 つ

プロセスの水準での PID と錠は 1 つだけです(既定のホームの下にある、多重化のプロセスのもの)。既定プロファイルでの hermes status --full は多重化のプロセスを報告し、受け持っているプロファイルを並べます(Serves: coder, research)。hermes -p coder status と hermes -p coder gateway status は「停止中」ではなく「既定プロファイルの多重化を通して動作中」と報告します。ダッシュボードの /api/status?profile=coder と Channels のページは、多重化のプロセスを coder の動いているゲートウェイとして報告し、プラットフォームには coder 自身のアダプタが並びます。唯一の gateway_state.json は既定のホームの下にあり、従属側のアダプタはそこに served_profiles と並んで <profile>:<platform> の項目として現れます。プロファイルごとのゲートウェイの状態ファイルは書かれません。

hermes -p coder cron status はホストのゲートウェイ 1 つと、それが受け持つプロファイルを名指しで表示し(Scheduler host: the host gateway (PID 4211) serving profiles default, coder)、そのうえで coder 自身の刻みの鼓動と、最後に成功した刻みを確かめます。鼓動が無かったり古かったりするときは、動作中と言い切らずに警告を出します。cron list と cron create も、受け持っているプロファイルの鼓動が新しくないときは警告します。cron status は、この軽い確認では読まない刻みの失敗の詳細まで見せます。

ホストの役を持つゲートウェイが無いときは、cron status は 1 つだけ のホストのゲートウェイを起動し(hermes --profile default gateway install / gateway run)、それがこのプロファイルを受け持つようにしてください、と案内します。プロファイルごとのサービスを入れる方法は、LEGACY (pre-multiplex topology, not recommended) の下にだけ示されます。それを入れると、ホスト上に 2 つ目のゲートウェイのプロセスが立つからです。hermes doctor も同じ決まりに従います。s6 の下では、プロファイルごとの枠の数ではなく Host gateway: the host gateway (PID 4211) serving profiles default, coder と報告し、まだ監督下にあるプロファイルごとの枠は LEGACY として指摘します。受け持たれているプロファイルから doctor を実行したときも、ホストの systemd ユニットの linger を確かめます。state.db を掴んでいるプロセスの行もホストの共有プロセスを名指しするので、「3 process(es) holding the DB open」と出たときに、どのゲートウェイで、止めるとどのプロファイルに響くのかがわかります。

変わら ない こと

プロファイルごとの .env による認証情報の切り離しはそのままで、むしろ厳しく なります。プロファイルの鍵はそれぞれの範囲から解決され、共有の環境にまとめて 流し込まれることはありません。MCP サーバーやかんばんの作業役のような子プロセスが 見るのは、自分のプロファイルの秘密情報だけです。外部の秘密情報の供給元 (1Password、Bitwarden など)から入る認証情報も同様で、プロファイル B のために 起動した stdio の MCP サーバーは、その名前について B の値を受け取るか、B に無ければ 何も受け取りません。既定プロファイルの値が渡ることはありません。MCP サーバーは プロファイルごとに つながります。2 つのプロファイルがどちらも github という 名前のサーバーを自分のトークンで持っていれば接続は 2 本になり、それぞれ自分の 道具しか見えません。mcp_servers の項目が同一(経路 *も* 認証情報も同じ。mTLS の client_cert/client_key も含みます)のプロファイル同士は 1 本の接続を分け合い、持ち主が /reload-mcp すると、相乗りしている側は読み直さずに道具が登録し直されます。auth: oauth のサーバーは、プロファイルをまたいで分け合うことがありません。プロファイルごとに自分の mcp-tokens/ の下へ自分のトークンを持ち、自分の接続を開きます。起動時はプロファイルを 1 つずつ順に接続し、 1 つのプロファイルの中では同時に最大 mcp.discovery_concurrency 個のサーバーまで接続します(既定は 4、0 で無制限)。 そのため、stdio のサーバーを多く持つプロファイルがいくつあっても、補助のプロセスが一斉に立ち上がることはなくなりました。信頼の方針はプロファイルごとのままです。 trust: full のプロファイルの接続を相乗りしている trust: untrusted のプロファイルは、書き込みのできる 呼び出しのたびに引き続き確認を求められ、supports_parallel_tool_calls もそれを設定したプロファイルにだけ効きます。端末まわりの設定(terminal.backend、 terminal.cwd、terminal.docker_volumes、terminal.docker_shared_container_key、 SSH の接続先など)も、振り分けられたやり取りごとにプロファイル単位で解決されます。 端末の設定を書いていないプロファイルには文書どおりの既定が使われ、起動元の プロファイルの値が使われることはありません。config.yaml や .env を読み取れない プロファイルは、ほかのプロファイルの隔離方針で動かすのではなく、端末での実行を 断られます。メディアの送付にかかる認証情報の守り(MEDIA: の添付の裏にある拒否 一覧 —— .env、auth.json、config.yaml、state.db、セッションの記録、OAuth の トークンの保管庫)は profiles/ の下のすべてのプロファイルを覆うので、どの プロファイルのやり取りでも、ほかのプロファイルの秘密情報やチャットの履歴を返信に 添えることはできません。権限もプロファイルごとです。GATEWAY_ALLOW_ALL_USERS、 GATEWAY_ALLOWED_USERS、そしてプラットフォームごとの許可一覧や全員許可の指定は、 持ち主のプロファイルの .env から読まれます。既定プロファイルが全員に開いても 従属側のボットが開くことはなく、従属側が自分の .env だけで開いた指定はきちんと 効きます。プロファイルの config.yaml に書いたボットごとの挙動(require_mention、 mention_patterns、allow_bots、reactions、auto_thread、free_response_auto_thread、dm_policy、 ignored_channels、Matrix の session_scope など)も同じです。従属側の YAML が 共有のプロセスの環境に入り込むことはないので、それが既定プロファイルの方針になる ことはありませんし、既定プロファイルの YAML が従属側のボットを支配することも ありません。terminal.env_passthrough の許可一覧、元宝が自動で決める定位置の チャンネル、各プロファイルの config.yaml を守る書き込みの制限も、プロファイル ごとに解決されます。かんばん、プロファイル単位のスキル・記憶・SOUL、モデルの 振り分けは、どれもゲートウェイを分けたときとまったく同じようにプロファイル単位で 振る舞います。

外向きの名乗りもプロファイルごとです。プロファイル P のために動いている やり取りが send_message の道具(送信、反応、メディア)を呼べば、投稿は P 自身の ボットから出ます。P のセッション宛ての「Gateway shutting down/restarted」や /update のお知らせ、P のチャットから仕掛けた /loop の呼び出し、P の Discord の ボットで権限のないスラッシュコマンドが使われたときの運用者への知らせ(P の 定位置のチャンネルへ)も同じです。P にそのプラットフォームのボットがつながって いなければ、送信ははっきりしたエラーで失敗します。既定プロファイルのボットに 落ちることはありません。

道具と記憶の供給元の認証情報も同じ決まりに従います。外部の OCR (FIRECRAWL_API_KEY)、Modal や Browser Use のクラウドの入口、mem0 OSS の OpenAI の 鍵、xAI の動画、そして記憶の供給元ごとの識別情報(MEM0_USER_ID、 SUPERMEMORY_CONTAINER_TAG、RETAINDB_PROJECT、OPENVIKING_ACCOUNT/USER、 HINDSIGHT_BANK_ID、HERMES_HONCHO_HOST)は、振り分け先のプロファイルの .env から 読まれます。ですから従属側のプロファイルの記憶は そのプロファイルの アカウント・保管先・プロジェクト(または供給元のプロファイルごとの既定)に入り、 既定プロファイルのものに混じることはありません。独自のエンドポイントも鍵と一緒に 動きます —— OPENAI_BASE_URL、XAI_BASE_URL、NOUS_INFERENCE_BASE_URL、 GATEWAY_PROXY_URL、Firecrawl や Browserbase や RetainDB や Supermemory や Honcho や Hindsight の URL —— なので、あるプロファイルの鍵がほかのプロファイルの中継役や 自前のサーバーに送られることはありません。WEIXIN_HOME_CHANNEL、 HERMES_LANGUAGE と display.language、hooks.outbound[].secret_env も同じく プロファイルごとで、追い出された従属側のセッションの終わりに走る記憶の抽出も、 そのプロファイルの範囲で動きます。

やり取りごとの実行時の設定も、振り分け先のプロファイルに従います。 agent.max_turns、fallback_providers、file_read_max_chars、tool_output.*、 browser.* の待ち時間、timezone(execute_code の隔離環境に渡す TZ を含む)、 メディアの送付の方針(gateway.strict、media_delivery_allow_dirs、 trust_recent_files*)、補助的な呼び出しに使う Nous の auth.json は、すべてその やり取りを受け持つプロファイルから読まれます。ゲートウェイを起動したときの プロファイルから読まれることはありません。プロファイルごとの状態のファイル (processes.json、checkpoints/、隔離環境の控えの保管庫、飛書のコメントの 規則と紐付け)とゲートウェイのフックも同じです。フックは各プロファイルの hooks/ ディレクトリが個別に読み込まれ、そのプロファイルの出来事にだけ反応します。 シェルのフックは振り分け先のプロファイルの HERMES_HOME で動き、環境に既定 プロファイルの秘密情報は入りません。標準入力で渡される中身には、どのプロファイルが 発火させたかを示す profile の欄が入ります。

プロファイルごとに切り離されるもの

多重化されたやり取りが 自分の プロファイルから解決し、既定やほかのプロファイルと 決して共有しないものの早見表です。

対象 どこから解決するか そのプロファイルに無いときの振る舞い
プロバイダーの鍵、ボットトークン、config.yaml の ${VAR} 参照 そのプロファイル自身の .env(自分の秘密情報の範囲) 未解決、またはアダプタ無し。既定プロファイルの値が使われることはない
権限(GATEWAY_ALLOW_ALL_USERS、GATEWAY_ALLOWED_USERS、プラットフォームごとの許可一覧と全員許可の指定) 持ち主のプロファイルの .env と config.yaml 閉じたまま。既定プロファイルで開いても従属側のボットは開かない
スラッシュコマンドの関門(allow_admin_from、user_allowed_commands、group_allow_admin_from。スラッシュコマンドを参照) メッセージを受け取ったボットのプロファイル。従属側のボットを律するのはそのプロファイル自身のプラットフォームの extra ブロックで、既定プロファイルのものではない 閉じる側に倒れる。多重化プロセスが設定をまだ読み込んでいない受け持ち中のプロファイルは、管理者の一覧を空、利用者に許可したコマンドも無しとして関門にかけられ、常に許される最低限(/help、/whoami)だけが動く。既定プロファイルの開いた方針が使われることはない
HTTP の宛先(/p/<profile>/api/...、/p/<profile>/webhooks/...、プラットフォームの出来事の呼び返し) 名指しされたプロファイルの API_SERVER_KEY、profile: で結び付けた Webhook の経路、そのプロファイル自身のアダプタ 401 か 404。アダプタが無いままの送付は 502 か 503 で、ほかのプロファイルのボットは使われない
受信ポート型のプラットフォーム(/p/<profile>/webhooks/twilio、/p/<profile>/line/webhook、/p/<profile>/api/messages など) 名指しされたプロファイル自身のアダプタとその合言葉(Twilio の認証トークン、LINE のチャンネルのシークレット、Teams のアプリ、BlueBubbles のパスワードなど)。返信もそのアダプタから出る 合言葉が違えば 401 か 403、そのアダプタを持たないプロファイルなら 404。既定プロファイルのアダプタが使われることはない
アダプタの設定(*_REQUIRE_MENTION、*_REACTIONS、*_ALLOW_BOTS、*_PROXY、Discord の allow_mentions、Matrix の allowed_users / ignore_user_patterns、Webhook のホスト・ポート・URL、Matrix のスレッド・セッション・E2EE の方針、Discord の遡り取得と添付の上限、Buzz の返信の形、A2A のエージェントカードと公開 URL、WhatsApp のブリッジの方針、Yuanbao のホームチャンネル) 持ち主のプロファイル。明示した .env の値 → そのプロファイルの config.yaml → アダプタの既定、の順 そのアダプタの文書どおりの既定。既定プロファイルの設定が使われることはない。プロファイルが 1 つだけの環境では、各プラットフォームのページに書かれているとおり、環境変数が YAML より勝つ決まりがそのまま保たれる
MEDIA: の添付の拒否一覧 profiles/ の下のすべてのホームと既定のホーム(確認のたびに数え上げる) どのやり取りも、ほかのプロファイルの .env、auth.json、state.db、セッション、トークンの保管庫を添付できない
stdio の MCP の子プロセスの環境 安全な土台 + 秘密情報の供給元の名前についてそのプロファイルの範囲の値 + サーバー自身の env: そのプロファイルに無い名前は子プロセスにも無い。既定プロファイルへ落ちることはない
外向きの送信(send_message、停止・再起動・/update のお知らせ、/loop の呼び出し、profile: で結び付けた Webhook の送付、github_comment のトークン) そのプロファイル自身のつながっているアダプタと .env はっきり失敗する。既定プロファイルのボットから投稿されることはない
セッションの名前空間 agent:<profile>:…(既定は agent:main:… のまま) 同じチャットにいる 2 つのプロファイルが履歴を共有することはない
ログ そのプロファイル自身のホームの下の agent.log / errors.log / gateway.log —
端末の隔離の設定(terminal.*、SSH の接続先) そのプロファイルの config.yaml 文書どおりの既定。読み取れない設定なら実行を断る
やり取りの作業ディレクトリ(terminal.cwd が未設定のとき) 単独のゲートウェイと同じ決まり。ローカルの実行先なら $HOME、それ以外は隔離環境の既定 多重化のプロセスを起動したディレクトリになることはない
コマンドの承認(command_allowlist、「always」の選択) そのプロファイル自身の config.yaml 既定プロファイルの「always」が従属側のコマンドを前もって承認することはない。従属側の選択はその自分の設定に保存される
隔離環境への認証情報ファイルのマウント(terminal.credential_files)、security.redact_secrets、browser.* のエンジンと画面表示の指定、lsp.*、補助のプロバイダーの健康状態の印、logs/mcp-stderr.log そのプロファイル自身の config.yaml / .env 文書どおりの既定。起動元のプロファイルでキャッシュした値が使われることはない
クラウド SDK の認証情報のクライアント(Bedrock の boto3 クライアントとモデルの探索、Azure Entra の認証情報)、認証情報で取ってくる一覧(DeepInfra、Copilot の文脈の上限、Nous の推論の上限、Ramp Router の effort、xAI / OpenRouter の画像モデル、独自エンドポイントの /models)、Camofox の VNC のアドレス、computer-use の補助の視覚の振り分け、リモートの実行先の確認の文面、学習した画像のトークンの費用、display.skin、ゲストの発行の待ち、バナーのスキル、元宝の「使用中」のアダプタ、Langfuse のクライアント そのプロファイル自身の .env / config.yaml / <home>/cache 文書どおりの既定。起動元のプロファイルでキャッシュした値やその認証情報が使われることはない
セッション検索のつまみ(sessions.cjk_fts、sessions.search_slow_ms) そのプロファイルの config.yaml 文書どおりの既定。既定プロファイルから橋渡しされた値が使われることはない
RoomLink の機能の一覧と、それが離れた Bot に示す署名付きの実行方針(approvals.mode、agent.max_turns、platform_toolsets.api_server) 依頼が名指しした、受け持たれているプロファイル(/p/<profile>/v1/room-members/...、RPC の profile 引数)。どの一覧にも target_profile が 必須 で、HERMES_PROFILE に頼ることはない 招待や機能の取得は、問題の target_profile を名指しして失敗する。存在しないプロファイルは断られ、起動したプロファイルの設定から解決されることはない
プラットフォームのプロキシ(TELEGRAM_PROXY、DISCORD_PROXY、HTTPS_PROXY など) そのプロファイル自身の .env 直接つなぐ。既定プロファイルのプロキシが使われることはない
デスクトップやダッシュボードの裏側での MCP の探索 受け持つプロファイルのホームごとに 1 回 ほかのプロファイルがすでにエージェントを作ったあとで選ばれたプロファイルも、自分の mcp_servers を探索する
デスクトップや TUI のセッションから変えた設定(/busy、/verbose、/approval、/cwd、テーマや表示の切り替え) そのセッションを持っているプロファイルの config.yaml。RPC がセッションの ID しか運んでいないときも同じ 書かれるのはセッション自身のプロファイル。立ち上げたプロファイルの config.yaml とその TERMINAL_CWD には触れない
デスクトップやダッシュボードの裏側と、プロファイルごとの cron の刻み役での MCP の接続 gateway.multiplex_profiles が切られていても、受け持つプロファイルごとに分ける。多重化と同じ決まり 名前が同じでも認証情報が違う mcp_servers の項目は、別の接続になる。受け持つプロファイルが、ほかのプロファイルとしてサーバーを呼ぶことはない
ダッシュボードの操作(デスクトップやダッシュボードが立ち上げる hermes -p <name> …) そのプロファイルの HERMES_HOME に固定した、掃除済みの子プロセスの環境 子プロセスは自分の .env を読む。ダッシュボードのプロファイルのトークンやポートは引き継がれない
受け持たれているプロファイルのために動くすべての子プロセス(スラッシュコマンドの作業役、Bot Chat の配達、A2A の転送、key_cmd の補助、ブラウザーの操作役) 認証情報を掃除した土台の上に、そのプロファイル自身の .env と秘密情報の出どころを重ねたもの。gateway.multiplex_profiles の有無を問わない(デスクトップやダッシュボードの ?profile= の経路も含む) 子プロセスには入らない。systemd / Compose / シェルを通じて起動プロセスにだけ届いた鍵が、ほかのプロファイルの子プロセスに引き継がれることはない
ほかのプロファイルのために立ち上げた子プロセスでの、許可の関門(*_ALLOWED_USERS / *_ALLOWED_CHANNELS / *_IGNORED_CHANNELS / *_ALLOW_ALL_USERS / *_ALLOW_BOTS、GATEWAY_ALLOW*)。ダッシュボードの hermes -p <name> の操作、かんばんの作業役、Bot Chat の配達、更新後のプロファイルごとの gateway restart 子プロセス自身の .env と config.yaml。子プロセスが自分で読む 閉じた状態(アダプタに書かれている初期値)。unit ファイルやシェルから立ち上げ側のプロセスに渡された関門は、子プロセスが始まる前に落とされるので、プロファイル B がプロファイル A のトークや利用者の一覧を当てはめることはない。同じプロファイルの子プロセスはそのまま引き継ぐ
古い読み手のために、受け持っているプロファイルを実行中の HERMES_HOME 環境変数へ写す組み込み先(Hermes WebUI)での、振り分け先のプロファイルの判定 組み込み先が hermes_constants.pin_process_hermes_home() で固定した起動時のホーム。MCP の接続の鍵、受け持たれているプロファイルの子プロセスでの起動環境の除去、橋渡しされた全員許可の初期値、terminal.* の環境の橋渡しの守りは、どれもこれと比べる 固定しなければ、実行中の環境変数がそのまま起動時のホームで、これまでとまったく同じ。HERMES_HOME を書き換えない組み込み先は何もしなくてよい
ほかのプロファイルも受け持つ hermes serve やダッシュボードのプロセスでの、起動した(既定の)プロファイル自身の認証情報 ほかのプロファイルを初めて受け持った瞬間に固定された プロセスの環境の上に、その .env と秘密情報の出どころを重ねたもの。そのあとは読み直さない プロセスの環境だけで入れ替えた認証情報(systemctl set-environment、再実行しなかった op run の包みの更新)は、プロセスを再起動するまで反映されない。入れ替える鍵は .env か秘密情報の出どころに置くか、入れ替えたあとに再起動する
cron の .env での調整(HERMES_CRON_TIMEOUT、HERMES_MODEL の代替、HERMES_CRON_MAX_PARALLEL、事前入力のファイル)、作業役や Bot Chat の子プロセスの環境 そのプロファイル自身の .env。子プロセスが既定プロファイルの .env の設定や橋渡しされた TERMINAL_* の方針を引き継ぐことはない cron の既定、またはモデルの拒否。単独の hermes -p <name> gateway run とまったく同じ
プロファイルのタスクのかんばんの作業役と知らせ 担当者の .env + config.yaml(道具一式の固定、端末の実行先、メディアの方針、表示の言語) —
/loop の刻み、background_process_notifications の関門、notice_delivery、裏で動くプロセスのチェックポイントからの復帰 持ち主のプロファイルの state.db / config.yaml / processes.json —

設計上 共有される もの: プロセス本体、その PID と錠と gateway_state.json (既定のホーム)、1 つの HTTP の待ち受け、そして profile_routes の表 (既定プロファイルで宣言します)。

どのプロファイルが受け持たれるか

gateway.multiplex_profiles: true は、既定プロファイルに加えて profiles/ の下にある 生きている名前付きプロファイルを すべて 受け持ちます。ただし、自分の config.yaml で gateway.standalone: true にして外れたものは除きます。単独のプロファイルは自前のゲートウェイを 動かし、ホストに数え上げられません(プロファイルごとのゲートウェイは新しく作らない を見てください)。(以前の gateway.multiplex_profile_allowlist の設定は廃止されました。 設定の移行で config.yaml から取り除かれます。受け持たせたくないのに外れていないプロファイルは、 代わりに保管へ回すか削除します。hermes profile delete <name> を使うか、そのディレクトリを profiles/ の外へ移してください。)削除したプロファイルは墓標を残し、数え上げられる ことはありません。ディレクトリの無くなったプロファイルが、受け持ちのやり取り、 cron の刻み役、ログの振り分けによって作り直されることもありません。

受け持ちの範囲は、/p/<profile>/ の API と Webhook の接頭辞、実行中の状態表示、 プロファイルへの振り分けの対象、そしてプロセス内の cron の並べ役がどのプロファイルを 刻むかを決めます(デスクトップの裏側の刻み役も周回のたびに同じ範囲を数え直し — デスクトップの動作中に作ったり消したりしたプロファイルは、再起動しなくても刻む対象に加わったり外れたりします — 動いている多重化か そのプロファイル自前のゲートウェイがすでに受け持っているプロファイルからは手を引きます)。 hermes -p <name> gateway run として起動した多重化は、自分のプロファイルの cron の 保管庫も必ず刻みます。

受け持ちの範囲は その場で追随 します。多重化が動いている間に作られたプロファイル (hermes profile create、ダッシュボード、デスクトップ、TUI のどれで作っても)は、 すぐに受け持たれます。作る側が制御用のソケットで多重化に知らせ、多重化のほうも 念のため 30 秒ごとに profiles/ を見直すからです。新しいプロファイルのアダプタは、 その config.yaml か .env にボットトークンが入った時点で作られます(作る側はたいてい、 先にプロファイルを作ってからトークンを足します)。既定プロファイルの gateway_state.json の served_profiles が更新され、hermes -p <name> gateway status は受け持ち中と報告します。再起動は要らず、ほかのプロファイルのアダプタや 進行中のやり取りにも影響しません。プロファイルを削除すると、同じようにそのアダプタが 止まり、振り分けからも外れます。hermes profile rename では、ディレクトリを動かす前に 古い名前を振り分けから外し、新しい名前を再起動なしで受け持ちます(古い名前に結び付いたままだった アダプタや cron の刻み役が、その名前を生き返らせることはありません)。認証情報 1 つにつき問い合わせ役は 1 つ、という決まりは 変わりません。ほかのプロファイルのトークンを使い回して後から足されたプロファイルは、 duplicate_credential のエラーで止め置かれ、2 つめの問い合わせ役として動き出すことは ありません。

その場で受け持ちを増やすと、多重化しているプロセスのプロファイルの確保も広がります。 新しいプロファイルの gateway.lock を取り、そのプロファイル専用のセッションの管理元を組み立てるので、 hermes -p <name> は多重化しているプロセスを持ち主として見つけます(プロファイルごとのデーモンが はぐれて立ち上がることはありません)。次の hermes gateway restart でも、全体をきれいに確保し直せます。 state.db を開けないプロファイルは止め置かれ、Profile '<name>' not served とログに出て served_profiles から外れますが、ほかのプロファイルは起動を続けます。保存先を直してから hermes profile create をもう一度実行するか、定期的な再走査を待ってください。

共有のボットのチャットをプロファイルへ振り分ける(profile_routes)

多重化は 認証情報ごと(プロファイル自身のボットトークン)か、URL の接頭辞ごと (HTTP のプラットフォームなら /p/<profile>/)にプロファイルを選びます。複数の コミュニティが 1 つの ボットトークンを分け合う場合、たとえば 1 つの Discord の ボットが多くのサーバーを受け持つ場合には、gateway.profile_routes で特定の サーバー・チャンネル・スレッドを別々のプロファイルへ振り分けることもできます。

gateway:
  multiplex_profiles: true
  profile_routes:
    # An entire Discord server → one profile
    - name: acme-server
      platform: discord
      guild_id: "1234567890"
      profile: acme

    # One channel in that server → a different profile
    - name: acme-support
      platform: discord
      guild_id: "1234567890"
      chat_id: "9876543210"
      profile: acme-support

    # A Telegram group (no guild concept — chat_id only)
    - name: tg-group
      platform: telegram
      chat_id: "-1001234567890"
      profile: tg-profile

    # A WhatsApp DM — write the phone number; JID and LID forms also match
    - name: owner-whatsapp
      platform: whatsapp
      chat_id: "15551234567"
      profile: owner

    # One Teams user across DMs, groups, and channels (exact sender id)
    - name: teams-owner
      platform: teams
      user_id: "00000000-0000-0000-0000-000000000000"
      profile: owner

振り分けは、条件ごとの点数を足し合わせた細かさで照合されます。user_id が 16、 thread_id が 8、chat_id が 4、guild_id が 2 です。つまり user_id + chat_id (20)は user_id だけ(16)より優先され、それは場所だけを鍵にした振り分け (最大でも 14)より優先されます。書いた条件はすべて満たす必要があります (かつ、の関係です)。点数が同じなら書いた順が保たれ、チャンネルを鍵にした 振り分けは、そのチャンネルを親に持つスレッドやフォーラムの投稿にも当たります。 どの振り分けにも当たらないメッセージは、既定の(いま動いている)プロファイルに 残ります。振り分けられたプロファイルには、上で説明したプロファイル単位の切り離しが すべて効きます(設定、スキル、記憶、認証情報、セッションの名前空間)。振り分けは Discord だけでなく、すべてのプラットフォームのアダプタで働きます。

user_id は届いたメッセージの送り主で、完全に一致するかどうかで照合します。 その値の確かさは、報告してくるアダプタの確かさを超えません。ですから、送り主を 認証している入口を持つプラットフォームでだけ、これを権限の判断材料として扱って ください。送り主の id は、プラットフォームによっては相手先ごとに別の空間で 振られます。Slack の利用者の id はワークスペースの中でだけ通じる値です。 そのため、複数のワークスペースやサーバーを受け持つゲートウェイでは、id だけに 頼らず、user_id にその範囲の guild_id(Discord のサーバー、Slack の ワークスペース、Matrix のサーバー)を組み合わせてください。

user_id を書かなければ、これまでどおり送り主で絞り込まない振り分けになります。 null、空の文字列、空白だけを設定した場合は、そのプラットフォームの全員に広がる のではなく、その振り分け自体が無効になります。

送り主による振り分けは、プロファイルを選ぶ仕組みであって、既定で拒否する権限の 仕組みではありません。どの振り分けにも当たらない送り主は、振り分けのない チャンネルとまったく同じように、既定の(いま動いている)プロファイルへ流れます。 ひとりだけに強い権限のプロファイルを与え、ほかの全員を制限されたプロファイルに したいときは、強い権限の送り主の振り分けを先に書き、そのあとにプラットフォーム 全体を受ける振り分けを制限されたプロファイルへ向けて置き、プラットフォーム自身の 入口の許可一覧もそのまま残してください。

振り分けは、bot_profile: <profile> でほかのボットを名指ししない限り、既定 プロファイルのボット が受け取ったメッセージにだけ効きます。Telegram の個別の やり取りはどのボットでも同じ chat_id(利用者の id)になるので、これが無いと、 共有のボット向けに書いた chat_id の振り分けが、その利用者と従属側の専用ボットとの 個別のやり取りまで拾ってしまいます。従属側のプロファイル自身のボットに届いた メッセージは、そのプロファイルに留まります。

    # Pin one user's DM with team_b's OWN bot to a third profile
    - name: teamb-owner-dm
      platform: telegram
      bot_profile: team_b
      chat_id: "72719239"
      profile: ops-for-team-b

振り分けられたメッセージの権限は、常に 受け取ったボットのプロファイル(その トークンと許可一覧)で決まります。エージェントが手一杯のときに送られた追加の メッセージや、/topic や /stop のようなやり取りの途中の確認も同じです。 振り分け先のプロファイル自身に許可一覧の写しは要りません。自分のボットを持たない 振り分け先のプロファイルも、ゲートウェイの再起動後は共有のボットを通して裏からの 知らせ(処理の完了、生存の合図、非同期の委任の結果)を受け取ります。

WhatsApp と WhatsApp Cloud では、chat_id の振り分けは利用者の識別の書き方をまたいで 当たります。素の電話番号(15551234567)、JID(15551234567@s.whatsapp.net)、 LID(…@lid)は、橋渡しがそれらを結び付けたあとは同じ人を指します(セッションの鍵や アダプタの許可一覧がすでに使っているのと同じ正規化です)。profile_routes に電話番号を 書いておけば、WhatsApp が JID を届けても LID を届けても、受信した個別のやり取りは 当たります。LID の対応付けがまだ無いうちも、番号の書き方は JID には当たりますが (末尾が取り除かれます)、知らない LID は解決できません。その受信は、対応付けが できるまで既定プロファイルへ流れます。グループのチャット(…@g.us)は送り手の 識別ではないので、これまでどおり厳密に一致したときだけ当たります。Telegram の 数字の id は変わりません。

profile_routes には gateway.multiplex_profiles: true が要ります。多重化が無効なら 振り分けは無視されます。明示した振り分けに当たったものの、その宛先のプロファイルが 入っていない(または削除された)場合、ゲートウェイは その受信を拒否し、振り分けと宛先を記録に残します。既定プロファイルで動かすことは しません。どの振り分けにも当たらない通信は、これまでどおり既定プロファイルの 振る舞いのままです。

振り分けられたプロファイルが持つ cron の仕事も共有のボットから送られますが、送り先は、 有効な振り分けの chat_id か thread_id によってそのプロファイルに結び付いている ものに限られます(guild_id + chat_id の振り分けは、そのチャンネルを 対象として認めます)。振り分けられたプロファイルの仕事が、振り分けのないチャットや 別のプロファイルに振り分けられたチャットを宛先にしている場合、共有のボットから 送られることはありません。サーバーだけを書いた振り分けは cron の宛先として認められない ので、送り先のチャンネルについて chat_id の振り分けを足してください。user_id を 書いた振り分けも同じく認められません。cron には認証された送り主が届かないので、 場所だけを鍵にした振り分けを別に用意する必要があります。このとき、 振り分けられたプロファイル側に platforms.<platform> のブロックは要りません。共有の ボットの権限は、衛星側の設定ではなく振り分けから来るからです。

すべてのゲートウェイをまとめて起動・停止・再起動する

CLI にはプロファイル 1 つ分の起動停止のコマンドが付いています。全プロファイルに 効かせたいときは、シェルの繰り返しで包みます。下の断片を ~/.local/bin/hermes-gateways に置き、chmod +x してください。

#!/bin/sh
set -eu

# Add or remove profile names here as you create / delete profiles.
profiles="default coder personal-bot research"

usage() {
  echo "Usage: hermes-gateways {start|stop|restart|status|list}"
}

run_for_profile() {
  profile="$1"
  action="$2"
  if [ "$profile" = "default" ]; then
    hermes gateway "$action"
  else
    hermes -p "$profile" gateway "$action"
  fi
}

action="${1:-}"
case "$action" in
  start|stop|restart|status)
    for profile in $profiles; do
      echo "==> $action $profile"
      run_for_profile "$profile" "$action"
    done
    ;;
  list)
    hermes gateway list
    ;;
  *)
    usage
    exit 2
    ;;
esac

そのうえで、次のように使います。

hermes-gateways start      # start every configured profile
hermes-gateways stop       # stop every configured profile
hermes-gateways restart    # restart all
hermes-gateways status     # status across all
hermes-gateways list       # delegates to `hermes gateway list`

プロファイルを 1 つだけ操作する

プロファイルを作ると入る近道のコマンドは次のとおりです。

coder gateway run        # foreground (Ctrl-C to stop)
coder gateway start      # start the managed service
coder gateway stop       # stop the managed service
coder gateway restart    # restart
coder gateway status     # status
coder gateway install    # create the LaunchAgent / systemd unit
coder gateway uninstall  # remove the service file

これらは hermes -p coder gateway <action> と同じです。プロファイルの別名が PATH に無いときや、スクリプトのなかで宛先を動的に変えたいときに役立ちます。

サービスのファイル

プロファイルはそれぞれ固有の名前で自分のサービスを入れるので、ぶつかることは ありません。

プラットフォーム 置き場所
macOS ~/Library/LaunchAgents/ai.hermes.gateway-<profile>.plist
Linux ~/.config/systemd/user/hermes-gateway-<profile>.service

既定プロファイルは従来の名前のままです。ai.hermes.gateway.plist と hermes-gateway.service です。

ログを見る

プロファイルはそれぞれ自分のログのファイルに書きます。

# Default profile
tail -f ~/.hermes/logs/gateway.log
tail -f ~/.hermes/logs/gateway.error.log

# Named profile
tail -f ~/.hermes/profiles/<name>/logs/gateway.log
tail -f ~/.hermes/profiles/<name>/logs/gateway.error.log

すべてのプロファイルのログを同時に流すには、次のようにします。

tail -f ~/.hermes/logs/gateway.log ~/.hermes/profiles/*/logs/gateway.log

CLI には構造化されたログの閲覧機能もあります。

hermes logs -f                  # follow default profile
hermes -p coder logs -f         # follow one profile
hermes logs --help              # filters, levels, JSON output

実際に何が動いているかを確かめる

hermes profile list             # profiles + model + gateway state
hermes-gateways status          # full status across every profile
launchctl list | grep hermes    # macOS — PIDs and labels
systemctl --user list-units 'hermes-gateway-*'   # Linux — units

設定を編集する

プロファイルは自分のディレクトリのなかに設定を持ちます。

~/.hermes/profiles/<name>/
├── .env              # API keys, bot tokens (chmod 600)
├── config.yaml       # model, provider, toolsets, gateway settings
└── SOUL.md           # personality / system prompt

既定プロファイルは ~/.hermes/ を直接使い、同じ 3 つのファイルを持ちます。

好きなエディタでも、CLI でも編集できます。

hermes config set model.model anthropic/claude-sonnet-4    # default profile
coder config set model.model openai/gpt-5                  # named profile

.env や config.yaml を編集したら、対象のゲートウェイを再起動してください。

coder gateway restart
# or, for everything:
hermes-gateways restart

ホストを眠らせない

ゲートウェイのプロセスは一日中動けますが、OS のほうは手すきになると眠ろうとします。 やり方は 2 つあります。

macOS — caffeinate

caffeinate は macOS に最初から入っていて、動いている間はスリープを止めます。 入れる作業は要りません。

caffeinate -dis                    # block display, idle, and system sleep
caffeinate -dis -t 28800           # same, auto-exit after 8 hours
caffeinate -i -w $(cat ~/.hermes/gateway.pid) &   # awake while default gateway runs

# Persistent: run in background and forget
nohup caffeinate -dis >/dev/null 2>&1 &
disown

# Inspect / stop
pmset -g assertions | grep -iE 'caffeinate|prevent|user is active'
pkill caffeinate
オプション 効果
-d 画面のスリープを止める
-i 手すきのときのシステムのスリープを止める(既定)
-m ディスクのスリープを止める
-s システムのスリープを止める(電源につないだ Mac のみ)
-u 利用者の操作があるように見せる(画面のロックを防ぐ)
-t N N 秒後に自動で終了する
-w P PID が P のプロセスが終わったら終了する

Linux — systemd-inhibit か loginctl

# Inhibit suspend while a command runs
systemd-inhibit --what=idle:sleep --who=hermes --why="gateways running" \
  sleep infinity &

# Allow user services to keep running after logout (recommended)
sudo loginctl enable-linger "$USER"

居残りを有効にすると、systemd のユーザー単位(hermes-gateway-<profile>.service を 含む)は SSH が切れても再起動をまたいでも動き続けます。

トークンのぶつかりを防ぐしくみ

プロファイルは、プラットフォームごとに固有のボットトークンを使わなければなりません。 2 つのプロファイルが Telegram、Discord、Slack、WhatsApp、Signal のトークンを 分け合っていると、2 つめのゲートウェイはぶつかっているプロファイル名を挙げた エラーを出して起動を断ります。多重化の もとでは、同じ決まりが重複したプロファイルのアダプタだけを止め置き、共有の ゲートウェイは動き続けます。

確かめるには、次のようにします。

grep -H 'TELEGRAM_BOT_TOKEN\|DISCORD_BOT_TOKEN' \
     ~/.hermes/.env ~/.hermes/profiles/*/.env

プロファイルごとのゲートウェイから移行する

いまプロファイルごとに自前のゲートウェイを動かしている(多重化だけになる前の版から続く、 プロファイル 1 つにつき systemd の単位か launchd のエージェントが 1 つの形)なら、既定のゲートウェイの 起動時の事前確認が、まとめ終わるまで単独のままにします(未設定の既定値が、動いている一式を二重に 掴むことはありません)。本当の境目が妨げない限り(下を参照)、hermes update がまとめてくれます。 同じまとめはコマンド 1 つでもでき、途中まで移行した環境(フラグは有効なのにユニットが残っている、 2 つの間で落ちた、など)で実行し直すと、「already multiplexed」と報告する代わりに続きを終わらせます。

hermes gateway migrate --multiplex --dry-run   # print the plan and any blockers; changes nothing
hermes gateway migrate --multiplex             # apply (asks for confirmation on a TTY; -y skips)

--standalone のような逆向きのコマンドはありません。プロファイルごとの一式は、目指す構成として 扱われないからです。妨げられた一式はそのまま動き続け、どのプロファイルも hermes -p <name> gateway install --force が道筋のままです。あるいは gateway.standalone: true でホストのゲートウェイから外れます(プロファイルごとのゲートウェイは新しく作らない を見てください)。hermes gateway migrate --multiplex はこの設定を尊重します。

Docker / Hermes Cloud(s6 が監視するコンテナ)

公式のイメージのなかでは、どのプロファイルにも s6 の枠 (/run/service/gateway-<profile>)があります。コンテナの起動処理は *名前付きの* 枠をすべて down として登録し、その自動起動の意図を root の枠にまとめるので、まっさらな起動の時点で もう多重化しています。その場での 更新でも、揃えるためにコンテナを再起動する必要は なくなりました。hermes gateway migrate --multiplex(と hermes update が走らせるフック)は、まだ up になっている名前付きの枠を止め(s6-svc -d に加えて、 監視役が再起動しても生き返らないよう down ファイルを置きます)、起動処理と同じ決まりで その意図を root の枠にまとめ、root の枠を再起動します。down として登録された枠は妨げに なりません。妨げになるのは、実際に up になっている枠だけです。このコマンドがなかから できないのは、起動処理が一度も登録しなかった root の枠を作ることだけです。その場合はそう 名乗ったうえで、コンテナの再起動を求めます。

hermes update がすること

更新がうまくいったあと、プロファイルが 2 つ以上あり、少なくとも 1 つの従属側 プロファイルが自前のゲートウェイ(動いているプロセスか、入っているサービス)を持ち、 gateway.multiplex_profiles が無効なら、hermes update は同じ事前確認を走らせます。

  • 妨げるものが無い → 移行が自動で走り(hermes gateway migrate --multiplex --yes と

同じ処理の道筋です)、何をしたかを表示します。結果は決まっていて確認を求めることも ないので、画面の無い環境や cron での更新でも走ります。

  • 妨げるものがある → 警告の欄に、妨げるものそれぞれの正確な直し方と、あとで

実行する 1 行のコマンドが並びます。何も変更されません。

プロファイルが 1 つだけの環境は移行されません(得るものが無いからです)。すでに 多重化している環境もそのままです。ほかのプロファイルが自分のゲートウェイを動かしていないときも、 hermes update は何もしません。何も動いていなかった環境でモードを切り替えることはありません。

hermes update が自分からは越えない境目

無人で動くフックがまとめるのは、UNIX ユーザーが 1 つ、サービスの管理範囲が 1 つ、profiles/ の ツリーが 1 つ のプロファイルだけです。hermes profile create で作るとこの形になります。 独立して動くほかのプロファイルが、次のいずれかの境目の向こうにあると、自動の移行は止まります。

境目 例
サービスの管理役や範囲が違う 既定はユーザーの systemd、ほかのプロファイルはシステムの systemd(または launchd)。あるいは既定は切り離して動かし、ほかのプロファイルはサービスの管理下
1 つのプロファイルに入れたユニットが 2 つ以上ある 同じプロファイルにユーザーのユニットとシステムのユニットがある(はっきりしたコマンドなら両方を消します)
UNIX ユーザーが違う 自分の User= を持つシステムのユニット、または別の uid が持ち主の動いているゲートウェイ。このホストで User= を解決できないシステムのユニットは — 従属側 でも 既定側でも — 「わからない」として扱い、「同じユーザー」とは見なさない
HERMES_HOME が <default home>/profiles/ の外にある HERMES_HOME=/opt/hermes/profiles/emma を固定したユニット

この場合、hermes update は見つけた境目と hermes gateway migrate --multiplex を表示するだけで、 何も変えません。ユニットは消されず、プロファイルごとのゲートウェイは動き続けます(こうした境目がまとめるのを妨げる場合は --force が引き続き道筋です。境目の無いプロファイルは gateway.standalone: true で外れます)。こうした構成を ひとつにまとめると、カーネルが守る境目(ファイルの持ち主、User=)を、プロセスの中での分離に 置き換えることになります。それを決めるのは運用する人です。はっきりしたコマンドなら、それでも移行できます。 同じ発見は hermes gateway migrate --multiplex --dry-run に注意として出るので、先に読めます。 確認したうえで --multiplex を実行すれば進みます。

自動の移行を断る

この環境で自動のまとめを一切走らせたくないときは、既定のプロファイルで gateway.auto_multiplex_migration: false を設定します。

hermes config set gateway.auto_multiplex_migration false

こうすると hermes update は、環境がどれほど移行の条件に合って見えても、プロファイルごとの ゲートウェイをそのまま残し、何も表示せず、何も変えません。この設定は config に残るので、 更新しても消えません。決めるのは一度きりで、リリースのたびに蒸し返されることはありません。 ほかの設定と同じく、実際に効いている config から読むので、管理側の範囲(/etc/hermes/config.yaml)で 固定した値は、プロファイル自身のファイルより優先されます。効くのは自動の経路だけです。hermes gateway migrate --multiplex ははっきりした依頼なので、 それでも移行します(断ったあとで改めて移行するときも、これを使うのが正式なやり方です)。 設定が無いか true なら、上で説明した既定のふるまいのままです。

はっきりしたコマンドのほうは違います。プロファイルが 2 つ以上あり、独立して動くほかのゲートウェイが 1 つも無いときに hermes gateway migrate --multiplex を実行すると、残っているひと手間だけを 行います。gateway.multiplex_profiles: true を設定し、既定のゲートウェイを(再)起動します。 多重化を頼んだのだから、多重化になる、ということです。

移行ですること

  1. 従属側のプロファイルそれぞれの単独のゲートウェイを止め、そのサービス(systemd の

ユーザー単位かシステム単位、または launchd のエージェント)を外します。外したものは、 元に戻すために ~/.hermes/gateway_migration.json に記録されます。

  1. 既定 プロファイルの config.yaml に gateway.multiplex_profiles: true を設定します。
  2. 既定のゲートウェイを再起動します。あるいは従属側が使っていたのと同じサービス管理の

しくみに入れて起動するので、systemd で管理していた一式は systemd の管理のままです。

  1. 既定のゲートウェイがすべてのプロファイルを含む served_profiles を記録するまで待ち、

まとめを表示します。

妨げるものと直し方

妨げるもの 理由 直し方
2 つのプロファイルが同じプラットフォームの認証情報を設定している(たとえば同じ TELEGRAM_BOT_TOKEN) 1 つのプロセスのなかでは、ボットトークン 1 つにつき問い合わせは 1 回しかできません。多重化は重複したほうを止め置き、そのプロファイルのボットは黙ってしまいます 2 つめのプロファイルからトークンを外すか、default に置いたまま、そのプロファイルのチャットを profile_routes で振り分けます
従属側のプロファイルが、既定の待ち受けに /p/<profile>/ の入口を 持たない、ポートを掴むプラットフォームを有効にしている 多重化はそのプロファイル全体を飛ばします(決まり 2 を見てください) そのプロファイルでプラットフォームを無効にする(platforms.<name>.enabled: false)か、そのプロファイルを単独で動かします。自分の config.yaml に gateway.standalone: true を設定し、ホストが走査し直すのを待つ(長くて 30 秒)か、ホストに rescan-profiles の制御コマンドを送ります。hermes -p <name> gateway install --force は、境目がまとめるのを妨げる場合にだけ使ってください。

認証情報の確認はゲートウェイ自身のぶつかりの検出を使い回すので、その判定は多重化が 起動時にすることと一致します。どのポートを掴むプラットフォームが /p/<profile>/ の 入口を持つかはアダプタ自身から読み取る(それぞれが serves_profile_prefix を 宣言します)ので、HTTP で受ける新しいアダプタが接頭辞に対応しても、事前確認は 正しいままです。

受信ポート型のプロファイルで変わること

api_server や webhook を自分のポートで使っていた従属側のプロファイルは、移行を 妨げません。ただし URL が変わります。事前確認は新しい正確な URL を表示します。 たとえば次のとおりです。

Profile 'coder': api_server moves onto the default listener at
http://127.0.0.1:8642/p/coder/v1/... (its key/secret is unchanged; update
clients that call the old per-profile port).

そのプロファイル自身の API_SERVER_KEY や Webhook の合言葉は、接頭辞付きの URL でも 引き続き認証に使えます。鍵について変わることはほかにありません。

移行後に作ったプロファイル

多重化が動いている間に作ったプロファイルは、再起動なしで受け持たれます(上を見て ください)。hermes profile create は、動いている多重化がプロファイルを拾ったときは そのことを確かめて伝えます。hermes gateway restart を促す表示が出るのは、多重化に 届かなかったとき(たとえば古い版から起動したゲートウェイ)だけです。

失敗したときと再開

移行は取り消せる作りです。計画の段階で失敗が見えているもの(User= の記録が無いのに root で動かす必要があるシステムのユニット、書き換えられない設定ファイル)は、プロファイルごとの ゲートウェイを 1 つも止める前に断ります。記録のファイルを書いたあとに失敗したもの — フラグの書き込み、 後に続く従属側の停止やユニットの削除、既定のゲートウェイの導入や起動 — は、その場で記録のファイルを たどって元に戻すので、ゲートウェイの無いプロファイルは残りません。そのあいだのどこかでプロセスが落ちた場合は、 次に hermes gateway migrate --multiplex を実行したときに、フラグが有効で、記録のファイルがあり、 移行したプロファイルを受け持つ多重化が動いていない(入れてあっても止まっている既定のユニットは数えません)ことを 見つけ、「already multiplexed」と報告する代わりに、記録のファイルから続きを進めます。ディスクに記録のファイルがあれば、 それは常に *まだ終わっていない* という意味です。これは再開のための記録で、元に戻すコマンドではありません — --standalone のような逆向きのコマンドはなく、上で説明した取り消しは、1 回の適用が失敗したときにその中でだけ走り、 ゲートウェイの無いプロファイルを残さないためのものです。

自動では扱わないもの: s6 で監督しているコンテナ — 次にコンテナを起動したときに揃います (プロファイルごとの枠は停止として登録され、ルートのゲートウェイが多重化します。ただし gateway.standalone: true のプロファイルは、自分の実行の意図にもとづいて自分の枠を起動します)。Windows のタスク スケジューラの タスクは、コマンドがまとめます。事前確認で移行できる環境と わかったときは、ダッシュボードの System のページにも同じ移行がボタンとして出ます。

コードを更新する

hermes update は最新のコードを 1 回取ってきて、新しく同梱されたスキルを すべてのプロファイルへ配ります。

hermes update
hermes-gateways restart

動いているゲートウェイは更新そのものが再起動します。まだプロファイルごとに ゲートウェイを 1 つずつ動かしている環境では、続けて更新が 多重化したゲートウェイ 1 つへの移行を実行します。 妨げるものが無ければ自動で行い、あれば境目(UNIX ユーザーが違う、HERMES_HOME が profiles/ の外にある)と、 自分で実行する 1 行のコマンドを示した警告として出します。

手を入れたスキルが上書きされることはありません。

困ったときは

「Could not find service in domain for user gui: 501」と出る

hermes gateway stop のあとに hermes gateway start を実行しています。CLI の stop は launchctl unload まで行うので、サービスが launchd の登録簿から消えます。 CLI は start のときにこのエラーだけを捕まえて、plist を自動で読み込み直します (↻ launchd job was unloaded; reloading service definition)。サービスはそのまま 起動します。直す必要はありません。

落ちたあとに PID が残っている

プロファイルのゲートウェイが not running と出ているのに、プロセスがまだ生きて いる場合は、次のようにします。

ps -ef | grep "hermes_cli.*-p <profile>"
cat ~/.hermes/profiles/<profile>/gateway.pid
kill -TERM <pid>          # graceful
kill -KILL <pid>          # if that fails after a few seconds
<profile> gateway start

サービスを 1 つだけ強制的に入れ直す

# macOS
launchctl unload ~/Library/LaunchAgents/ai.hermes.gateway-<profile>.plist
launchctl load   ~/Library/LaunchAgents/ai.hermes.gateway-<profile>.plist

# Linux
systemctl --user restart hermes-gateway-<profile>.service

健康診断

hermes doctor                  # default profile
hermes -p <profile> doctor     # one profile