Skip to content

サンドボックスバックエンド

源码版本v2026.7.20

職務

agent はコマンドを実行し、コードを走らせるが、任意のコマンドをホスト機に直接叩かせてはいけない。Hermes のターミナルツールは 6 種の実行バックエンドをサポートし、安全/隔離/コストのトレードオフで選ぶ:Local(本機直実行)、Docker(コンテナ隔離)、SSH(遠隔機)、Singularity(HPC コンテナ)、Modal(クラウドサンドボックス (cloud sandbox)、direct または managed)、Daytona(クラウド開発環境)。コンテナ硬化 + 名前空間隔離で、危険コマンドは承認コールバックでブロックする。

設計動機

なぜ 1 種ではなく 6 種なのか? agent の実行シナリオは幅が広い——開発時は本機で素早く走らせたい、本番バッチはコンテナ隔離が必要、HPC ユーザーは(Docker を使えず)Singularity で走らなければならない、クラウドの無人運転は Modal のような秒課金クラウドサンドボックスが要る。単一のバックエンドでは覆えない。Hermes は「実行」を統一インターフェースに抽象し、バックエンドをシナリオで組み立て、危険コマンドの承認コールバックはバックエンドをまたいで共用する。Docker バックエンドは bind mount がホストパスを露出するかを専用に検出する——コンテナ隔離の安全の前提は「コンテナは外を見えない」ことで、docker run -v /:/host された時点で崩れるからだ。Modal は direct(ユーザー自身のクレデンシャル)と managed(ゲートウェイ (gateway) 管理)を区別する——前者はユーザーの請求、後者は Nous ポータルのクレジットに計上され、権限と課金パスが全く違う。これらの検出ロジックはバックエンドの中には書かず、tool_backend_helpers.py に抽出し、バックエンド選択と安全レベル判定を疎結合にする。

主要ファイル

データフロー

  1. agent がコマンド実行を決める → tool_call terminaltools/terminal_tool.py
  2. ジョブ設定でバックエンドを選ぶ(local/docker/modal/ssh/singularity/daytona):
  3. 危険コマンドは承認コールバックでブロック——主ループは対話的承認。サブエージェントはデフォルト auto-deny(tools/delegate_tool.py:75)、cron/バッチは opt-in で auto-approve 可能。
  4. コマンド出力を主ループに戻す。長タスクはバックグラウンド化可能(tools/daemon_pool.py)。

バックエンドリストの宣言はシンプルで、terminal_tool.py の冒頭コメント一行に過ぎない:「A terminal tool that executes commands in local, Docker, Modal, SSH, Singularity, and Daytona environments.」

Docker バックエンドは bind mount がホストパスかを検出する。spec の書き出しを見る:/~./../ で始まるか、Windows ドライブレター C:\ のように二文字目がコロンなら、ホストパスとみなして has_host_access=True にし、以降の権限はより厳しい分岐に流れる。

python
def _docker_volume_uses_host_path(volume_spec: str) -> bool:
    """Return True when a docker volume spec bind-mounts a host path."""
    if not isinstance(volume_spec, str):
        return False
    vol = volume_spec.strip()
    return bool(vol) and (
        vol.startswith(("/", "~", "./", "../")) or
        (len(vol) >= 3 and vol[1] == ":" and vol[2] in ("/", "\\"))
    )

Modal 解析は三段階:direct(ユーザー自身の Modal クレデンシャル)、managed(Nous ポータル管理)、auto(デフォルト、managed 不可時に direct にフォールバック (fallback))。resolve_modal_backend_state が一箇所に集まり、バックエンドを選べなければ None を返す。has_direct_modal_credentialsMODAL_TOKEN_ID/MODAL_TOKEN_SECRET 環境変数と ~/.modal.toml ファイルの両方を見て、両方欠けて初めて direct 不可とする。managed_nous_tools_enabled は Nous Portal アカウントに問い合わせ、失敗時は fail closed で、問い合わせエラーで誤って managed パスを開けない。

Singularity は HPC シナリオの特殊な存在だ——ユーザーは Docker を入れられず、Apptainer/Singularity しか使えない。_find_singularity_executable はまず apptainer を探し、次に singularity を探し、両方なければインストール先を明示したエラーを返す。scratch ディレクトリは /scratch(HPC クラスタの標準マウント)を優先し、なければ get_sandbox_dir() / "singularity" に落ちる。コンテナは --containall --no-home + capability dropping で走らす——HPC 共有クラスタではこうしないと他のユーザーのホームディレクトリまで覗けてしまう。

境界と失敗

  • Docker bind mount の誤判定:検出関数は /~./../ で始まるものをホストパスとみなすが、named volume(mydata:/data など)は通してしまう。名前付きボリュームの背後に(カスタムドライバで)ホストパスがマウントされているとき、_docker_has_host_access には見えず、誤って低リスクと判定する。
  • Modal managed 失敗のフォールバック:auto モードで managed 不可時に direct にフォールバックするが、ユーザーが managed だけ設定し direct クレデンシャルを設定していないと、selected_backendNone になり、バックエンド選択の失敗が実行時に炸裂する。ターミナル作成時点で明確なエラーが出るとは限らない。
  • Singularity SIF キャッシュの膨張:SIF イメージは数百 MB から数 GB になる。scratch ディレクトリにローテーション方針がないとディスクを圧迫する。HPC ノードの /scratch はクラスタレベルのクリーンアップ方針を持つことが多く、両者が揃わないと「クラスタがキャッシュを消したのに agent は SIF がまだあると思う」事態になる。
  • 遠隔バックエンドのネットワークジッタ:SSH/Daytona のコマンド実行はネットワーク越しで、接続が切れると走行中の子プロセスの出力が失われる。出力回填は単一の同期戻りを前提とし、再接続時の再送がない。長タスクは遠隔バックエンドで最もリスクが高い。
  • タイムアウトと OOM:local/docker には script_timeout があるが、遠隔バックエンドのタイムアウトは遠隔プロセス自身が kill するのに頼る。ローカルスケジューラが応答を待てずに詰まるか、_run_job_script_with_claim_heartbeat のリース TTL で死んだと誤判定される。

まとめ

サンドボックス層は「実行隔離 + 危険コマンド管理」:6 バックエンドが本機から HPC、クラウドまでをカバー。Docker は bind mount を検出して権限昇格を防止、Modal は direct/managed のクレデンシャルを区別、危険コマンドは統一的に承認コールバックを経る。サブエージェントと cron は auto-deny/auto-approve の非対話パスを走る。

非公式コミュニティ学習サイト。MIT ライセンスの NousResearch/hermes-agent ソースに基づく。