---
license: "MIT. Translation of the Hermes Agent documentation, Copyright (c) 2025 Nous Research. See https://wiki.winsmux.dev/hermes/licenses.txt"
title: "gateway のローカルセッション"
description: "ローカルの CLI/TUI セッションを正規の gateway 受付として扱う仕組み"
upstream_path: developer-guide/gateway-local-sessions.md
upstream_blob: 8da45145e8ee63da6b57d081885e63929257a2a9
sources:
  - https://hermes-agent.nousresearch.com/docs/developer-guide/gateway-local-sessions
---

# gateway が所有する新規ローカルセッション {#gateway-owned-fresh-local-sessions}

組み合わせ済みの gateway リスナーは、`tui_gateway` 専用のエージェント実行環境を起動せずに、
新しいローカルの **CLI・TUI・GUI ポリシー** のセッションを作れます。実行、承認待ち、ツール呼び出し、
セッションの保存、正規のイベント配信は、既存の `GatewayRunner` / `TurnRunner` が受け持ちます。

サーバーで認証され、`session:create` を持つ ID は、次の呼び出しができます。

```json
{"jsonrpc":"2.0","id":1,"method":"session.create","params":{"request_id":"stable-client-create-id","source":"cli"}}
```

結果には `session_id`、`stored_session_id`、`messages`、`message_count`、
`info` がそのまま入り、権限側（authority）の通常の再開用スナップショットの項目も含まれます。2つの
セッション ID はどちらも、永続化された同じ固定の **論理** ID です。圧縮やリセットで非公開の
物理トランスクリプトの保存先が切り替わっても、クライアント側の ID はどちらも変わりません。作成時に
エージェントはすぐには立ち上がりませんが、返す前に実際の
SessionDB/SessionStore 上の ID は確保します。1つの正規の SQLite トランザクションで、
セッション行、経路、非公開の `gateway.local_policy.v1:<stored-id>` 作成受領記録をまとめて確定します。
同じ主体（principal）とプロファイルで同じリクエスト ID を繰り返すと、その ID と凍結済みの
ポリシーを再利用します。デーモンが落ちた後でも同様です。新しいセッションにするときは ID を省きます。
確認応答（ACK）が失われたときの再試行に備えたいクライアントは、ID を保持しておく必要があります。
トランザクションが失敗したときは、稼働中のセッションは何も公開されません。

認証済みの別のビューアーは `session.resume({session_id})` で接続でき、入力は
`prompt.submit({session_id, submission_id, text})` で送ります。受け付けた受領記録は永続的な
受付を意味するもので、推論の完了を意味しません。ビューアーをすべて閉じても、実行と保留中の承認は
生きたまま残ります。新しいビューアーは同じ ID で再開し、
`approval.respond({session_id, execution_generation, prompt_id, choice})` で応答できます。

## 意図的な挙動の変更: オペレーターの共有アクセス {#deliberate-behavior-change-shared-operator-access}

同じ gateway プロファイルの確認済みオペレーターは、別のオペレーター認証経路で作られた正規の
ローカルセッションにも接続できます。これらのオペレーターについては、作成者本人だけに限っていた
アクセス制限をこの仕組みに置き換えます。既存のダッシュボードの関門がすでにオペレーター権限を
与えているので、ネイティブの CLI/TUI から認証済みのダッシュボードへ移っても、2つ目の実行環境を
作ったり、生の ID が違うというだけで接続を拒んだりしてはいけないからです。共有するのはアクセスで、
ID ではありません。作成受領記録は元の所有者を保ち、受付と再試行は送信した主体を保ちます。

サーバーが `session:operator` を発行するのは、次の既存の信頼境界だけです。

- ネイティブの対話型ブートストラップチケットの引き換え（ワーカー引き取り用のチケットは除きます）。
- ダッシュボードの WebSocket アップグレードの関門。認証済みのダッシュボードのログインチケット、
  サーバーが起動した PTY クライアント向けのプロセス存続中だけ有効な内部認証情報、そしてその認証経路が
  有効なときの確認済みの旧来のセッショントークンを含みます。
- HTTP の変更系リクエストの関門。確認済みのダッシュボードの Session、ネイティブ HTTP の所有者許可、
  または許可された旧来のセッショントークンが受け入れられた後です。

クライアントが RPC パラメーターや ID の権限リストでこのスコープを要求することはできません。
オペレーターのスコープは、プロファイル、インスタンス、操作ごとの権限、購読、
リビジョン、世代の確認を置き換えません。メッセージング、ルーム、ワーカー、サービスの認証情報を
格上げすることもなく、所有者のいない過去のトランスクリプトを遠隔から最初に取得して引き取ることも
許しません。将来、制限付きのダッシュボード ID を導入する場合は、受け入れたダッシュボードの認証情報を
すべてオペレーターとして扱うのではなく、これらの発行の関門にその制限を反映させる必要があります。

別の主体によるローカルの送信には、キューでの実行と復旧のために、サーバーが書き込む非公開の
プロファイル/セッション/主体の紐付けが付きます。これはモデルへのリクエストにも、公開される
セッションのスナップショットにも出ません。この変更は、gateway リスナーを外部に公開するものではなく、
OpenAI API からローカルセッションへの紐付け（affinity）を実装するものでもありません。それらは別の連携経路のままです。

## 意図的に限定した互換性 {#deliberately-limited-compatibility}

- `source` は `cli`（既定）、`tui`、`gui`、`acp`、`tool`、`oneshot` を受け付けます
  （`gateway.session_policy.SURFACES`）。それぞれエージェントのプラットフォーム `cli`、`tui`、
  `desktop`、`acp`、`cli`、`cli` を選びます。`tool`（サードパーティの連携）と `oneshot`
  （`chat -q` / `-z` による1回きりの実行）は独自の保存ラベルを保つので、人が使う選択画面には表示されません。
  ネイティブのローカル経路の ID は、サーバーが所有する `Platform.LOCAL` のままです。source が
  メッセージングやネイティブの信頼を与えることはありません。TUI の既定の選択には `project` が加わり、GUI には
  クライアント画面用のツールセット（`project`、`desktop_ui`、`catalog`）が加わります。GUI ポリシーは
  `HERMES_DESKTOP` にも、接続するビューアーの ID にも左右されません。`toolsets` を明示しない場合、
  gateway は `session.create` が指定する `cwd` をもとに、コーディング向けの構え（コードのワークスペースでは
  `agent.coding_context: focus`、`agent/coding_context.py`）を適用します。これは作成時に1回だけ決まり、
  クライアントが選択として送ることはありません。適用するのは `session.create` だけで、`source: tool` には適用しません。
  Kanban のワーカー、cron ジョブ、A2A、ホストされたルーム、移行された旧形式の行は、プロファイルのツールセットを保ちます。
- 作成時に任意で指定できるフラットな項目は、`cwd`（gateway のローカルに既存の絶対パスのディレクトリ）、
  `model`（デーモンに設定されたプロバイダーでの空でないモデル識別子）、`toolsets`
  （確立済みのツールセット名を明示した配列。空の配列も可）です。未知のツールセットや
  ポリシーで除外されるツールセットは、黙って消えるのではなく拒否されます。明示したツールセットは
  画面ごとの既定の追加分より優先されます。CLI/TUI から `desktop_ui` を明示的に要求することはできません。
  `cwd`、モデルの選択、source、実際に有効なツールセットは作成時に記録され、接続時に
  変えることはできません。食い違う内容で `request_id` を繰り返すと `invalid_params` が返ります。
- ほかに凍結される起動時の項目（`CREATE_FIELDS`）は、`provider`、`base_url`（http(s) で、
  認証情報・クエリ・フラグメントを含まないもの）、`api_key`（所有者のメモリに保持され、永続化されません）、
  `reasoning`、`max_turns`、`skills`（凍結されるプロンプトに1回だけ書き込まれます。
  バイパス起動と一緒には指定できません）、真偽値の `yolo`、`checkpoints`、`accept_hooks`、`pass_session_id`、
  `ignore_rules`、`safe_mode`、`ignore_user_config`（safe mode は ignore-user-config を含み、
  ターンを隔離されたワーカーで実行します）、`editor`（ACP のみ）、そして `title`（既存を解決、無ければ作成）
  と `hidden` です。値の形式が正しくなければ `invalid_params` が返ります。これ以外の項目、cwd のワークツリー
  作成、履歴の事前投入、プロファイルの切り替え、サービス階層の選択は、引き続き
  `invalid_params` を返します。対応する起動項目のどれも、デーモン全体の設定やプロセスの
  環境変数を書き換えません。
- 新規作成には、サーバーで認証され `session:create` を持つ ID が必要です。この ID は
  関門付きのダッシュボードか、ネイティブのブートストラップ経路から渡されます。確認済みの旧来の `?token=` 経路は
  セッショントークンの ID を付与し、作成にも対応します。呼び出し側が RPC パラメーターで ID を
  渡すことはできず、オペレーターのスコープが再開専用の権限を
  `session:create` に格上げすることもありません。
- `session.list({limit})` は、許可された **稼働中** の権限側セッション（`scope: "live"`）を返します。
  ネイティブ TUI の選択画面は、権限側の導入前の CLI/TUI/GUI 履歴のために `session.list({limit, scope: "stored"})` も
  要求します。この読み取り専用の一覧は、ネイティブの旧来の再開と同じ所有者確認を使い、
  非表示・アーカイブ済み・内部用・他者所有の行を除外します。一覧に載せただけでセッションを引き取ることは
  ありません。引き取りは明示的な再開で行われ、既定の稼働中一覧は変わりません。
  `session.info({session_id})` は、source、現在のモデル、エージェントの遅延起動の状態、プロファイル ID を
  確認できる軽量で権限確認付きの表示です。
- `ping({})` は `{pong: true}` を返します。`runtime.describe({})` が公開するのは、権限側の ID、
  エポック、権限、作成の契約（`session_create.sources` と
  `session_create.parameters`。クライアントは起動オプションを送る前にこれを確認します）だけです。実行環境のプロトコルが
  完全に準備できたと主張することも、認証情報やパスを明かすことも、エージェントを起動することも、ターンのリースを更新することもありません。
- コールドリスタートでは、実行の前に、有効な非公開のローカルポリシーとサーバー所有の経路を復元します。
  作成受領記録は、認証済みの作成した主体とプロファイルに紐付いたままです。
  そのプロファイルの確認済みオペレーターは、この紐付けを置き換えずに正規のセッションにアクセスできます。
  呼び出し側の source やネイティブの文脈から、ローカル経路を再構成したり許可したりすることはできません。
  ポリシーや ID が欠けている、形式が壊れている、別のプロファイルのもの、食い違っている、のいずれの場合も、事前取得の確認を含めて安全側に倒して失敗します。
  受領記録を持たない、拡張以前の過去のセッションを、黙って CLI として扱うことはありません。
- 一度も開始していないキュー内のローカル入力は、ブートストラップの準備が整った後、同じ権限側の FIFO を通じて
  再開します。開始済みで中断された入力は `unknown` になり、再実行されることはなく、後続の入力を
  一時停止させます。再接続すれば、実行せずに unknown のスナップショットを確認できます。
- 圧縮の公開では、受領記録の非公開の `entry.session_id` と経路を、子トランスクリプトと一緒に
  不可分に進めます。受領記録は、作成時の `session_id`、ポリシー、
  主体/プロファイルの紐付け、明示的に承認された `lineage` を保持します。復旧時には、圧縮の
  段階を既存の正規の子セレクターで、リセットの段階を既存の
  `_reset_from` 境界で検証します。任意のフォークや、コピーされた他者の受領記録を引き取ることはありません。
- 正規の SessionStore のローカルリセットでは、新しい行、古い行のクローズ、受領記録、
  経路を確定してから、メモリ上の項目を変更します。リセットでは意図的に履歴を空から始めつつ、
  凍結済みの起動ポリシーとキュー内の受付の所有者は保ちます。中断された実行を再実行したり、
  後から受け付けた入力をキャンセルしたりはしません。独立した会話を作るには、引き続き
  新しいリクエスト ID で `session.create` を使います。これは、リビジョンで区切った新しいリセット用 RPC ではなく、
  複数ビューの `/new` の契約を変えるものでもありません。
- 受付の対象、ダイジェスト、取得の世代、購読者の ID、結果が不確かな状態は、
  固定の論理行に残ります。そのため、どちらの切り替えの後でも、ACK が失われたときの再試行は既存の受領記録を保ち、
  共有の操作やストリームのコールバックも同じ実行の所有者を保ちます。
  クライアントは引き続き、内部の物理的な継続 ID ではなく、作成時の ID で再開します。

## 検証 {#verification}

`tests/gateway/test_operator_cross_surface.py` は、ネイティブで作成してダッシュボードから接続するセッションと、
ダッシュボードで作成してネイティブから接続するセッションを、使い捨ての gateway プロセスの中で、実際の
ブートストラップと、ダッシュボードのログイン/コールバック/Cookie/チケットの経路を通して試します。ID プロバイダーと
モデルのエンドポイントはテスト用のフィクスチャです。クライアントの切断と再接続、FIFO の受付と
再試行、送信者の記録とモデルの文脈の保持、システムメッセージが変わらないこと、非公開の
オペレーターの由来情報が公開の応答やモデルへのリクエストに出ないことを確認します。gateway プロセスを
停止して再起動すること、同時の稼働中の配信を直接比べること、2つの認証方式をまたいで
承認や確認質問（clarify）を共有することは **試していません**。

`tests/gateway/test_session_operator_scope.py` は、スタブの実行器と一時停止したスケジューラーを使い、
新しい権限側のエポックをまたいでキューに入ったオペレーターの入力を扱います。これはユニットレベルの復旧の
証拠であって、実際の画面をまたぐコールドプロセス再起動の保証ではありません。後述する既存の復旧
フィクスチャは別の範囲を扱います。その結果を、
認証方式をまたぐ新しい gateway クラッシュのエンドツーエンドテストとして示してはいけません。

`tests/gateway/test_local_session.py` は、一時的な HOME と HERMES_HOME を持つ使い捨てのプロセスを起動します。
本番と同じ組み合わせ済みの HTTP/WS リスナー、実際の1回限りの認証済み
チケット、ループバックの OpenAI 互換モデル、実際の TurnRunner/AIAgent、実際の
terminal ツールを使います。永続化された ID と同じエージェントへの再接続を確かめ、承認が保留中のまま両方の
ビューアーを閉じます。再接続して同意したうえで、自分が所有する一時ディレクトリだけを削除し、
正規の履歴を確認し、偽造した認証/source/
プロファイルや未対応の起動項目を拒否することを確かめます。2つ目のケースでは実際の clarify ツールを動かし、
すべてのビューアーを閉じた後に再接続して、回答が次のループバックのモデルリクエストに届くことを確認します。
実行を許可しているのは、ネイティブのメッセージングの全許可（allow-all）の認証情報ではありません。
別の対照実験では、再構成したローカルの source/プロファイルのオブジェクトが引き続き安全側に倒して失敗することを
示す間だけ、メッセージングの全許可を有効にしています。

`tests/gateway/test_session_policy.py` は、所有するループバックのモデルに対して、CLI/TUI/GUI の3つのターンを
同時に走らせます。バリアで最初のリクエストを同時に保持し、実際の terminal
呼び出しが、所有する3つの作業ディレクトリにそれぞれ別のファイルを書きます。次のリクエストでは、
正しい cwd の結果、それぞれ異なる要求モデル、画面ごとのツールスキーマが見えます。
再接続しても各エージェントは保たれ、ランチャーの環境変数の受け渡しや設定ファイルのバイト列は
変わりません。これはループバックでの結合の証拠であって、ネイティブのランチャーやベンダーの証拠ではありません。
`tests/gateway/test_local_session_recovery.py` は、公開前のロールバックとコールド時の
受領記録の再利用を確かめた後、同じ一時データベース上で、実際の
コントロールソケットのチケットと認証済みの WebSocket を使って、通常のデーモンを別々に起動します。フィクスチャ専用のスケジューラーのバリアで、
入力の確定後かつ取得前に最初のデーモンを停止させ、別のモデルリクエストを
実際の取得の後で保持します。新しいデーモンは許可されたキュー内の入力だけを実行し、既定値が変わっていても
履歴/source/モデル/ツールセットを保ち、未知・他者・
欠落・破損のポリシーのケースを拒否します。さらに再起動しても、同じものを2回実行することはありません。推論は
すべてループバックのみです。これはネイティブのランチャーや圧縮の引き継ぎの証拠ではありません。

`tests/gateway/test_local_session_lineage.py` は、ロールバックと分離の不変条件に加えて、
3つの別々の通常のデーモンプロセスを扱います。最初のプロセスのフィクスチャは、実際のキャッシュ済み AIAgent で本番の
エージェント切り替えの公開を呼び、本番の SessionStore のリセットも呼んだうえで、
永続化されたキュー内の受付の後にデーモンを停止します。手を加えていないデーモンをコールドで起動すると、
圧縮された履歴、リセットによる空の履歴の境界、ポリシー/cwd、受付の ID、
unknown 状態による停止が保たれます。無関係なフォークの作業や他者の受領記録は引き取られないままです。相手役のモデルは、
復旧した cwd が実際のプロンプトに入っていることと、凍結済みのモデル/ツールセットを確認します。このフィクスチャは、
再起動後の cwd に対するツールの効果を実行せず、圧縮の要約も生成せず、
ネイティブのランチャーも試しません。証明するのは本番の公開/復旧の境界であって、それらの別の
範囲ではありません。
