この章で分かること
- Hermes が持つ 7 種類のターミナルバックエンド(実行環境)の全体像と、リスクの段階に沿った選び方
- Docker バックエンドが「なぜ安全とされるか」——ハードニングの中身と、セッション・新規会話・サブエージェントを跨いで共有され続ける単一の永続コンテナという挙動
- SSH・Modal・Daytona・Vercel Sandbox・Singularity/Apptainer それぞれが必要とする認証情報と永続化の仕組み
- サンドボックスに本物の API キーを持たせない仕組み iron-proxy(egress credential-injection proxy)の動き方と限界
- バックエンドを実際に切り替える手順と、切り替えたあとの後片付け
これは何か
「エージェントに shell を渡す」と聞いて身構えるのは自然な反応だ。Hermes のエージェントはターミナルツール経由で実際にコマンドを実行できるが、そのコマンドが物理的にどこで動くかを選べるのが、この章で扱う「ターミナルバックエンド」という仕組みである。
設定の入口は 1 つで、~/.hermes/config.yaml の terminal.backend キーである。
terminal:
backend: local # local | docker | ssh | modal | daytona | vercel_sandbox | singularity
変更は第 3 章で紹介した hermes config サブコマンドで行う。
hermes config get terminal.backend
hermes config set terminal.backend docker
2026-09 時点(v0.21.0)で選べるバックエンドは 7 種類。数え方には公式ドキュメント内でも表記のゆれがあり、アーキテクチャ図の注記は今も古い「6 backends」のままだが、設定リファレンス本文は「seven terminal backends」と明記している。本章では後者の 7 を正としたうえで、内訳を次節以降で見ていく。
バックエンドが決めるのは「コマンドがどこで動くか」だけではない。terminal ツール、ファイル操作ツール、execute_code の RPC(第 9 章)もすべて同じバックエンド上で実行される。バックエンドを Docker に切り替えると、これらのやり取りはすべて同じコンテナの中に閉じ込められる。
いつ使うのか
典型的な選び方は次のような問いに集約できる。
- 「とにかく手元で素早く試したい・隔離のコストを払いたくない」→ 隔離なしの
local - 「危険なコマンドの実行結果を人間の権限から切り離したい・安全なサンドボックスで CI/CD を回したい」→
docker - 「手元にない強力なマシン(GPU サーバーなど)で作業したい」→
ssh - 「評価やベンチマークを使い捨てのクラウド計算資源で回したい・管理されたクラウド開発環境やスナップショット保存が欲しい」→
modal/daytona/vercel_sandbox
Docker デーモンが使えない環境(HPC クラスタや、root 権限のない共有マシンなど)では singularity(Apptainer)が代わりになる。迷ったときの目安を表にまとめる。
| 状況 | 選ぶバックエンド |
|---|---|
| 個人のマシンで素早く試したい・隔離のコストを払いたくない | local |
| 危険なコマンドの結果をホストから隔離したい・安全なサンドボックスで CI/CD を回したい | docker |
| 手元にない強力なリモートマシンで作業したい | ssh |
| 評価・ベンチマークを使い捨てのクラウド計算資源で並列に回したい | modal |
| チームで管理されたクラウド開発環境を使い回したい | daytona |
| Vercel へのデプロイを前提に、スナップショットでファイルシステムを保存したい | vercel_sandbox |
| HPC クラスタや共有マシンで Docker デーモンが使えない | singularity |
7 つに共通する軸は「隔離の強さ」と「使い捨てかどうか」である。次節ではこの 2 軸に沿って、リスクの段階として整理する。
リスクの段階で見る 7 バックエンド
「エージェントに shell を渡すのが怖い」という感覚に対する Hermes の答えは、隔離をオール・オア・ナッシングにしないことだ。ホスト直実行からクラウドの使い捨て環境まで、隔離の強さを段階的に選べる。
各段階で何が変わるかを言葉でも確認しておく。local はエージェントに自分のユーザー権限をそのまま渡す。ssh は物理的に別のマシンへ実行を移すが、隔離の仕組み自体はそのマシンの通常のシェルと変わらない——ネットワークの向こう側にあるという境界だけが頼りになる。docker と singularity は OS のネームスペース機能でプロセスを閉じ込める箱を作り、docker はさらに Linux capability を丸ごと剥奪したうえで一部だけ復元する分だけ隔離が強い。modal / daytona / vercel_sandbox はクラウド上の VM・コンテナ・マイクロ VM そのものを使い捨て、セッションが終わればファイルシステムをスナップショットしてから消す。
次節では、初心者が最初に手を出しやすい docker の中身を詳しく見ていく。
Docker バックエンドの中身 — ハードニングと永続コンテナ
Docker バックエンドは、Hermes プロセスが動くホストの上で1 つの永続 Docker コンテナを起動し、以降のすべてのターミナル呼び出しをそのコンテナへの docker exec として実行する。「1 つ」という点が重要で、同じセッションだけでなく、/new による新規会話、/reset、delegate_task によるサブエージェント(第 9 章)まで、すべて同じコンテナを共有する。
この共有ぶりは徹底している。作業ディレクトリの変更、apt install や pip install で入れたパッケージ、/workspace 以下のファイル、そしてバックグラウンドで動かしたプロセスまで、次のツール呼び出しへ、さらには次の Hermes プロセスへとそのまま持ち越される。次に起動した Hermes プロセスは、コンテナに付いたラベル(hermes-agent / hermes-task-id / hermes-profile)による検索でこの同じコンテナを見つけ、数ミリ秒で再接続する。
コンテナが実際に止まる(docker rm -f される)のは次の場合に限られる。
- プロセスをまたぐ永続化を明示的に切っている —
docker_persist_across_processes: falseに設定している場合、毎回のクリーンアップで停止・削除される - アイドルリーパー — 既定 300 秒。ただし
persist_across_processes: falseのときだけ働く - 次回起動時のオーファンリーパー —
Exited状態のまま「アイドルリーパー秒数 × 2」(既定 600 秒)を超えたコンテナを掃除する - ユーザーによる明示操作 —
docker rm -f、docker system pruneなど
もう 1 つ、container_persistent: false に設定すると挙動が変わる点にも触れておく。これは「セッションごとに使い捨てのコンテナを作る」モードで、チャットやセッションが終わるたびにコンテナが破棄され、ファイルシステム・マウント・バックグラウンドプロセスは何も引き継がれない。会話を跨いだ隔離をセキュリティ境界として扱いたいときはこちらを使う。ただし delegate_task のサブエージェントは、このモードでも親セッションのコンテナをそのまま共有する。
セキュリティハードニングの中身は次のとおりである。
| 項目 | 設定内容 |
|---|---|
| Linux capability | --cap-drop ALL で全剥奪したうえで、DAC_OVERRIDE / CHOWN / FOWNER のみ復元 |
| 権限昇格 | --security-opt no-new-privileges |
| プロセス数 | --pids-limit 256 |
| 一時領域 | サイズ制限つき tmpfs(/tmp 512MB、/var/tmp 256MB、/run 64MB) |
公式イメージは nikolaik/python-nodejs:python3.11-nodejs20(Python 3.11 系・Node.js 20 系)が既定で、docker_image キーで差し替えられる。トークンやクレデンシャルは docker_env(config.yaml に直接書く固定値)と docker_forward_env(ホストのシェルや ~/.hermes/.env から値だけを引き継ぎ、値自体は config.yaml に残さない)で使い分ける。ネットワークを完全に遮断したいときは docker_network: false にすると --network=none の air-gap コンテナになる。
SSH・クラウド系バックエンドの使い分け
Docker の外側にある 5 つのバックエンドは、それぞれ必要な認証情報と永続化の仕組みが異なる。
| バックエンド | 必要な認証情報 | 永続化の方式 | 注意点 |
|---|---|---|---|
ssh | TERMINAL_SSH_HOST / TERMINAL_SSH_USER | ControlMaster で接続再利用+常駐シェルで cwd・環境変数を保持 | 5 分アイドルで keepalive。sudo や標準入力が要る操作は都度接続にフォールバック |
modal | MODAL_TOKEN_ID + MODAL_TOKEN_SECRET(または ~/.modal.toml) | ファイルシステムのみスナップショット | プロセスや PID 空間は保持されない |
daytona | DAYTONA_API_KEY | stop / resume 方式 | ディスク上限 10 GiB(Daytona 側の制約) |
vercel_sandbox | VERCEL_TOKEN + VERCEL_PROJECT_ID + VERCEL_TEAM_ID(開発中は短命な VERCEL_OIDC_TOKEN でも可) | ファイルシステムをスナップショット / 復元 | pip install 'hermes-agent[vercel]' が別途必要 |
singularity | なし(apptainer / singularity バイナリが必要) | 書き込み可能なオーバーレイが永続 | docker://... の URL は自動で SIF ファイルに変換・キャッシュされる。--containall --no-home で完全な namespace 隔離 |
いずれのバックエンドも container_persistent: true(既定)で「ファイルシステムを保存する」ことは約束するが、「同じライブなサンドボックス・PID 空間・バックグラウンドプロセスがそのまま動き続ける」ことまでは約束しない。ここが Docker バックエンドとの決定的な差になる。Docker は本物の永続コンテナをそのまま生かし続けるのに対し、クラウド系はいったん破棄してからファイルシステムだけを復元する方式である。
iron-proxy — サンドボックスに本物の鍵を渡さない
Docker サンドボックスの中でエージェントがプロンプトインジェクションに引っかかったとき、最悪のシナリオは「printenv | grep -i key で本物の API キーがそのまま盗まれる」ことだ。これを防ぐのが、Hermes がホスト側で動かすエッジプロキシ iron-proxy(egress credential-injection proxy)である。2026-09 時点では Docker バックエンドのみに配線されており、Modal / Daytona / SSH / Singularity にはまだプロキシ用の環境変数も CA 証明書も渡らない。
導入は hermes egress のサブコマンドで行う。
hermes egress install # 署名済みバイナリをダウンロード
hermes egress setup # ホスト側のCA生成 + プロバイダごとのプロキシトークンを発行
hermes egress start # プロキシデーモンを起動
hermes egress status # 状態確認
デフォルトで許可されているアップストリームは openrouter.ai / api.openai.com / api.anthropic.com / generativelanguage.googleapis.com / api.x.ai / api.mistral.ai / api.groq.com / api.together.xyz / api.deepseek.com / inference.nousresearch.com の 10 件で、ループバック・リンクローカル(クラウドのメタデータ API である 169.254.169.254 を含む)・RFC1918 プライベートアドレス・CGNAT といった SSRF 対策の拒否 CIDR も既定で効いている。
ただし万能ではない。リクエスト署名や SDK が発行する OAuth を使う認証方式はこの仕組みではカバーできない。AWS Bedrock / SageMaker の SigV4 署名、GCP Vertex AI のサービスアカウント OAuth がこれにあたり、該当する環境変数がサンドボックスに存在する場合は、その分だけ本物のクレデンシャルがサンドボックスの中に置かれることになる。
ハンズオン 10-1 Docker バックエンドに切り替えて、コンテナの中で動いていることを確認する
Docker Desktop(または Docker Engine)が起動している必要がある。初回はハンズオン中に既定イメージ nikolaik/python-nodejs:python3.11-nodejs20 のダウンロードが走るため、回線状況によっては数分かかる。切り替えると、以降のシェル実行はホストのユーザー権限では動かなくなることも意識しておく。
- 現在のバックエンドを確認する。
hermes config get terminal.backend
何も変更していなければ既定値の local が返るはずだ。
- Docker バックエンドに切り替える。
hermes config set terminal.backend docker
hermesを起動し、実行環境を確認するコマンドを投げる。
python3 --version
node --version
ホスト側と違うバージョンが返ってくれば、コンテナの中で実行されている証拠になる。既定イメージの名前がそのまま Python 3.11 系・Node.js 20 系であることを示しているので、ホストにこれと違うバージョンが入っていれば差は一目で分かる。
ハンズオン 10-2 単一コンテナがセッションをまたいで共有されることを確認し、後片付けする
- ハンズオン 10-1 のセッションの中で、
/workspaceにファイルを 1 つ作る。
echo "hermes-container-test" > /workspace/marker.txt
exitでいったん Hermes プロセスを終了し、hermesで新しいプロセスを起動し直す。そのうえで同じファイルを読む。
cat /workspace/marker.txt
プロセスを再起動しても中身がそのまま読めれば、それは同じコンテナに再接続できている証拠である。ホストを再起動したわけでもコンテナを作り直したわけでもなく、ラベルによる検索で同じコンテナに docker exec し直しているだけだからだ。
- 確認が終わったら、バックエンドを元の状態へ戻す。
hermes config set terminal.backend local
local に戻しても、起動したコンテナ自体はホスト上に残り続ける(既定では終了時に何もしない no-op のため)。動かしっぱなしにしたくない場合は、Docker のラベルで検索して明示的に削除する。
docker ps --filter label=hermes-agent=1
docker rm -f <コンテナ名またはID>
つまずきポイント
「バックエンド数」の表記はドキュメント間で割れている — アーキテクチャ図の注記は今も「6 backends」と書かれたままだが、設定リファレンス本文は明確に「seven terminal backends」としている。Vercel Sandbox を含めた 7 が正しい数字であり、古い図の注記に引きずられないよう注意する。
Docker バックエンドの「持ち越し」はバグではなく仕様 — セッションを終えても、新しい会話を始めても、Hermes プロセスを再起動しても、同じコンテナがそのまま使われ続ける。「前回インストールしたパッケージが残っている」「前回立ち上げたバックグラウンドプロセスがまだ動いている」という状態は想定内の挙動であり、まっさらな環境が欲しいときは container_persistent: false にするか、コンテナを手動で削除する必要がある。
iron-proxy は Docker 限定で、しかも全認証方式をカバーしない — Modal・Daytona・SSH・Singularity はまだプロキシの対象外で、これらのバックエンドでは本物のクレデンシャルがそのままサンドボックス(あるいはリモートマシン)に渡る。Docker バックエンドで使っていても、AWS SigV4 や GCP のサービスアカウント OAuth のような署名・OAuth ベースの認証は素通りしてしまう点も忘れてはならない。
クラウド系バックエンドの「永続化」はプロセスの生存を意味しない — container_persistent: true はファイルシステムのスナップショット / 復元を約束するだけで、同じライブなサンドボックスや PID 空間、動かしっぱなしのバックグラウンドプロセスが次回も生きている保証ではない。「さっき立てたサーバーがまだ動いているはず」という期待は、Modal / Daytona / Vercel Sandbox では外れる可能性が高い。
理解度チェック
Q1. 2026-09 時点(v0.21.0)でのターミナルバックエンドは何種類ですか。表記が古いドキュメント箇所はどこですか。
7 種類(local / docker / ssh / modal / daytona / vercel_sandbox / singularity)。設定リファレンス本文が正しい数字を示しているのに対し、architecture.md の図の注記は古い「6 backends」のままになっている。
Q2. Docker バックエンドで、セッションを終了しても新しい Hermes プロセスを起動しても同じコンテナが使われ続けるのはなぜですか。
コンテナには hermes-agent / hermes-task-id / hermes-profile のラベルが付与されており、Hermes プロセスは起動時にこのラベルで既存コンテナを検索して再接続するため。プロセス終了時のクリーンアップは既定では「何もしない」no-op で、コンテナ自体は動き続ける。
Q3. Docker バックエンドのセキュリティハードニングのうち、Linux capability に関する設定を説明してください。
--cap-drop ALL で全 capability をいったん剥奪したうえで、DAC_OVERRIDE・CHOWN・FOWNER の 3 つだけを復元する。加えて --security-opt no-new-privileges で権限昇格を防ぎ、--pids-limit 256 でプロセス数を制限する。
Q4. iron-proxy はサンドボックスにどんな値を持たせ、実際の API キーはどこで置き換えられますか。
サンドボックスには opaque なプロキシトークンだけを持たせる。ホスト側で動く iron-proxy デーモンが TLS を終端し、そのプロキシトークンを本物の API キーに置き換えてから外部のアップストリームへ転送する。
Q5. iron-proxy がカバーできない認証方式を 2 つ挙げてください。
AWS Bedrock / SageMaker が使う SigV4 署名リクエストと、GCP Vertex AI のサービスアカウントから発行される OAuth。どちらも静的なヘッダー置換では扱えないため、該当する環境変数があればサンドボックスに本物のクレデンシャルがそのまま渡ってしまう。
まとめ
- Hermes は 7 種類のターミナルバックエンドを持ち、
terminal.backendの 1 キーで切り替えられる。選び方の軸は「隔離の強さ」と「使い捨てかどうか」 - Docker バックエンドは cap-drop・no-new-privileges・pids-limit・tmpfs 制限でハードニングされた単一の永続コンテナを、セッション・新規会話・サブエージェントを跨いで共有し続ける
- SSH・Modal・Daytona・Vercel Sandbox・Singularity はそれぞれ異なる認証情報と永続化方式を持ち、「ファイルシステムは残るがプロセスは残らない」という共通の限界がある
- iron-proxy はサンドボックスに本物の API キーを渡さない egress プロキシだが、現状 Docker 限定かつ署名・OAuth ベースの認証はカバーできない
- 次の第 11 章では、この実行環境の上でエージェントがブラウザと Web をどう操作するかを扱う