Ubuntu Workshop で自律AIエージェント「HERMES Agent」を安全に隔離実行する
自律エージェントを
curl | bashでホストに直接入れるのは、正直こわい。 Canonical の新しい開発環境ツール Workshop(LXD ベース)でサンドボックス化し、 ホストとは必要なディレクトリだけ共有しながら HERMES Agent を動かしてみた記録です。検証環境: Ubuntu 24.04.4 LTS / Workshop 0.9.1 / LXD 6.8
TL;DR
- Workshop は LXD 上に再現可能な隔離開発環境を作る CLI。
workshop init/launch/execで完結する。 - HERMES Agent(Nous Research, MIT)は永続記憶・自動スキル生成・ブラウザ制御を持つ自律 AI エージェント。インストールは
curl | bash。まさに「ホストで直に動かしたくない」タイプのソフト。 - この2つを組み合わせ、ホストのファイルシステムからは隔離しつつ、明示した1ディレクトリだけ双方向共有する構成を作った。
- ハマりどころは4つ:①LXD 未導入 ②LXD のバージョン要件(6.8+、LTS の 5.21 では不可) ③mount インターフェースの作法 ④Docker 併用ホストでのブリッジ接続ハング。本記事はこの4つの解決を主軸に進む。
なぜサンドボックス化するのか
HERMES Agent は次のような性質を持つ。
- インストールが
curl -fsSL .../install.sh | bash(中身を全部読んでから入れる人は少ない) - LLM の指示で自律的にコマンドを実行する
- ブラウザ制御でウェブにアクセスする
- 73 個の組み込みスキルを持ち、ファイル操作・ネットワークアクセスを行う
便利だが、ホストの $HOME をそのまま触れる場所で動かすのは攻撃面が大きい。Workshop は公式に「実験的な AI ライブラリや自律エージェントを、攻撃面を絞って動かす」ユースケースを掲げている。今回はその通りの使い方をする。
ゴールはシンプルに3つ。
- Workshop の隔離環境(LXD コンテナ)の中で HERMES を動かす
- ホストの
$HOMEなど共有していない場所にはアクセスさせない - ただし、成果物のやり取り用に1ディレクトリだけ双方向共有する
登場ツール
| ツール | 役割 |
|---|---|
| Workshop | Canonical 製。YAML で環境を定義し、LXD 上に隔離開発環境を構築する CLI。workshopd デーモン + workshop CLI。SDK をプラグインのように足せる。 |
| LXD | Workshop のバックエンド。システムコンテナ/VM を管理する。6.8 以上が必須(後述)。 |
| HERMES Agent | Nous Research 製のオープンソース自律 AI エージェント(MIT)。CLI は hermes、メッセージング連携は hermes gateway。 |
ステップ0: Workshop と LXD の準備
sudo snap install workshop --classic
ここまでは済ませていた。しかし最初の workshop コマンドがいきなり失敗する。
error: cannot communicate with workshopd: ... connection reset by peer
ハマり① LXD が無いと workshopd が起動しない
デーモンのログを見ると原因は一目瞭然だった。
journalctl -u snap.workshop.workshopd.service --no-pager -n 20
cannot run workshop: cannot connect to LXD:
dial unix /var/lib/lxd/unix.socket: no such file or directory
Maybe LXD isn't installed?
To install LXD: 'sudo snap install lxd'
Workshop はバックエンドに LXD を必須とする。素直に入れる。
sudo snap install lxd
sudo lxd init --minimal # デフォルトのストレージプール + ブリッジを非対話で作成
sudo snap restart workshop # LXD ができたのでデーモンを再起動
ハマり② LXD のバージョン要件が厳しい(これが本記事最大の罠)
再起動して workshop list を叩くと、今度は別のエラー。
error: system is not healthy: incompatible backend:
LXD server version "5.21.4" is not supported; required >= 6.8.*
sudo snap install lxd のデフォルトチャンネルは LTS の 5.21/stable。安定志向で良さそうに見えるが、Workshop は LXD 6.8 以上を要求するため、これでは動かない。LXD 6 系は 6/stable チャンネルにある。
sudo snap refresh lxd --channel=6/stable
sudo snap restart workshop
lxd --version # 6.8
教訓: Workshop を入れるなら、最初から
sudo snap install lxd --channel=6/stableでよい。 公式ページの「LXD 6.8+」という要件は飾りではなく厳格にチェックされる。
これで workshop list のエラーが not a project(プロジェクト未作成)に変わった。バックエンド要件はクリアした合図だ。
補足:
systemctl is-active snap.workshop.workshopd.serviceがinactiveでも問題ない。 workshopd はソケット起動型で、CLI を叩いた瞬間に自動起動する。
ステップ1: プロジェクトと Workshop の作成
Workshop は「プロジェクトディレクトリ + .workshop/ 配下の定義 YAML」で動く。
mkdir -p ~/hermes-sandbox/shared # shared は後でホスト共有に使う
cd ~/hermes-sandbox
workshop init hermes --sdks uv
# → .workshop/hermes.yaml が生成される
生成された定義はシンプルだ。
# .workshop/hermes.yaml
name: hermes
base: [email protected]
sdks:
- name: uv
uv SDK を入れたのは、HERMES が内部で Python(uv)を使う想定だったため。実際にはインストーラが必要なものを自前で揃えるので必須ではないが、付けておいて損はない。
そして起動。
workshop launch hermes # ベースイメージを取得 → "hermes" launched
workshop list # STATUS: Ready
中に入れることを確認する。
workshop exec hermes -- bash -c 'whoami; echo HOME=$HOME; grep PRETTY /etc/os-release'
# workshop
# HOME=/home/workshop
# PRETTY_NAME="Ubuntu 24.04.4 LTS"
コンテナ内ではユーザー workshop、ホームは /home/workshop。これがサンドボックスの世界だ。
ステップ2: ホスト ⇔ サンドボックスのファイル共有(mount インターフェース)
ここが Workshop ならではの作法。Workshop の共有モデルは snapd 風のプラグ/スロットになっている。
- 各 SDK が mount プラグを宣言する。プラグは
host-source(ホスト側のパス)とworkshop-target(コンテナ内のパス)の対応を持つ。 - プラグは
mountインターフェースを介してsystem:mountスロットに接続される。
実際、最初から入れた uv SDK もキャッシュ用に mount を1本持っていた。
workshop info hermes
sdks:
uv:
mounts:
cache:
host-source: ~/.local/share/workshop/id/.../hermes/mount/uv/cache
workshop-target: /home/workshop/.cache/uv
workshop connections
# INTERFACE PLUG SLOT NOTES
# mount hermes/uv:cache hermes/system:mount -
つまり自分用の共有ディレクトリも、mount プラグを1本生やせば実現できる。
カスタム SDK を sketch-sdk で定義する
任意のホストディレクトリを共有するには、mount プラグを宣言する独自 SDK を作る。Workshop には試作用の sketch SDK(sketch-sdk)という仕組みがあり、エディタでテンプレートを編集して適用できる。
テンプレートの記法はこうなっている(抜粋)。
name: sketch
plugs:
# ホストディレクトリをマウントする
models:
interface: mount
workshop-target: /home/workshop/models
これに倣って、共有用プラグ shared を定義する。
name: sketch
plugs:
shared:
interface: mount
workshop-target: /home/workshop/shared
workshop sketch-sdk hermes
# → エディタが開くので上記を書いて保存
記事の再現性のため、
$EDITORを「目的の YAML を流し込んで終了する小さなスクリプト」にしておくと、 対話なしで適用できる。CI やドキュメント化に向く。
適用後、workshop info に sketch SDK と mount shared(target=/home/workshop/shared)が現れる。
永続化(eject)してプロジェクトに固定する
sketch のままだと試作扱いなので、--eject でプロジェクト SDK に昇格させる。
workshop sketch-sdk hermes --eject --name shared
# → .workshop/shared/sdk.yaml として保存される
# .workshop/shared/sdk.yaml
name: shared
plugs:
shared:
interface: mount
workshop-target: /home/workshop/shared
eject した SDK は、定義 YAML の sdks: に参照名 project-shared として追記して refresh する。
# .workshop/hermes.yaml
sdks:
- name: uv
- name: project-shared # ← 追記
workshop refresh hermes
小さな罠: 参照名は
project-sharedだが、インストール後の SDK 名・プラグ名はshared。 以降のコマンドではプラグをhermes/shared:sharedと指定する。
ホスト側ソースを実ディレクトリに向ける(remount)
refresh 直後の host-source は Workshop 管理下のデフォルトディレクトリを指している。これを自分の ~/hermes-sandbox/shared に差し替える。
workshop remount hermes/shared:shared ~/hermes-sandbox/shared
ここでもう一つの注意点。差し替え先が空でないディレクトリの場合、データ破損を防ぐためにワークショップを停止してから remount する必要がある。
workshop stop hermes
workshop remount hermes/shared:shared ~/hermes-sandbox/shared
workshop start hermes
remount で設定したソースは永続で、refresh 後も「最後に remount したソース」が使われる。最終的にこうなる。
mounts:
shared:
host-source: ~/hermes-sandbox/shared
workshop-target: /home/workshop/shared
双方向共有を検証する
# ホストで作成
echo "hello-from-host" > ~/hermes-sandbox/shared/from_host.txt
# サンドボックス内から読める
workshop exec hermes -- cat /home/workshop/shared/from_host.txt
# hello-from-host
# サンドボックス内で作成
workshop exec hermes -- bash -c 'echo hello-from-workshop > /home/workshop/shared/from_workshop.txt'
# ホストから読める
cat ~/hermes-sandbox/shared/from_workshop.txt
# hello-from-workshop
UID の対応も LXD の idmap が面倒を見てくれる。コンテナ内では workshop:workshop、ホストでは toming:toming の所有として、それぞれ自然に読み書きできる。
ステップ3: HERMES Agent をサンドボックスに入れる
ここでようやく、本来こわい curl | bash を安心して実行できる。隔離されているからだ。
workshop exec hermes -- bash -lc \
'curl -fsSL https://raw.githubusercontent.com/NousResearch/hermes-agent/main/scripts/install.sh | bash'
ハマり③ 特定ホストへの接続だけがハングする(Docker × LXD ブリッジ)
ところが、最初の実行で git clone が 132 秒でタイムアウトして失敗した。
fatal: unable to access 'https://github.com/NousResearch/hermes-agent.git/':
Failed to connect to github.com port 443 ... Couldn't connect to server
✗ Failed to clone repository
奇妙なのは「全滅ではない」こと。コンテナ内から各サイトへ curl して切り分けると、ホスト選択的な失敗だった。
| 接続先 | 結果 |
|---|---|
| raw.githubusercontent.com / pypi.org / nodejs.org | ✅ 通る |
| github.com / duckduckgo.com | ❌ タイムアウト(rc=28) |
DNS は正常(github.com は解決できている)。そしてホストでは Docker が複数コンテナを稼働中だった。これは Workshop が workshop warnings で最初から警告していた内容そのものだ。
firewall rules may be blocking network traffic on the workshopbr0 bridge:
the FORWARD chain policy is set to DROP ... likely caused by Docker.
何が起きていたか: Docker は iptables/nftables の FORWARD チェーンを DROP ポリシーにし、DOCKER-USER チェーンで自分のトラフィックだけを許可する。Workshop のブリッジ workshopbr0 経由の転送はこの網に引っかかる。さらに PMTUD(経路 MTU 探索)用の ICMP も落とされるため、大きな TLS 証明書チェーンを返すホスト(github.com=Azure 系など)への接続だけがハングし、CDN(Fastly)系の小さい応答は通る、という選択的な症状になっていた。
解決は、Workshop の警告が提示する nft ルールをそのまま入れるだけ。
sudo nft insert rule ip filter DOCKER-USER iifname workshopbr0 accept
sudo nft insert rule ip filter DOCKER-USER oifname workshopbr0 ct state related,established accept
| ルール | 意味 |
|---|---|
iifname workshopbr0 accept | Workshop ブリッジ発(コンテナ→外部)の転送を許可 |
oifname workshopbr0 ct state related,established accept | その戻りトラフィックを許可 |
適用後、コンテナ内から github.com / duckduckgo.com とも HTTP 200。原因が確定した。
注意: この nft ルールは再起動や Docker のネットワーク再構築で消える。 恒久化するなら、起動時にこのルールを再投入する仕組み(systemd unit など)を別途用意する。
インストール完了
ファイアウォール修正後に再実行すると、今度は素直に通った。
Done: 73 new, 0 updated, 0 unchanged. 73 total bundled.
✓ Skills synced to ~/.hermes/skills/
┌─────────────────────────────────────────────────────────┐
│ ✓ Installation Complete! │
└─────────────────────────────────────────────────────────┘
インストーラの挙動:
- Node.js 22 LTS を
~/.hermes/node/に自動導入(ブラウザツール用) - リポジトリを clone し、Python の仮想環境を構築
- 73 個のスキルを同梱、
~/.hermes/(約 1.1G)に config / .env / sessions / skills を配置 - 端末が無いため setup ウィザードはスキップ → LLM プロバイダ設定は後から
hermes setup
余談: base イメージで universe リポジトリが無効なため、
aptでripgrep/ffmpegが入らない。 どちらも任意機能(ファイル検索は grep にフォールバック)なので、今回はそのままにした。
ステップ4: 動作確認と隔離境界の検証
まずは HERMES 自体が動くこと。
workshop exec hermes -- bash -lc 'hermes --version'
# Hermes Agent v0.16.0 (2026.6.5) · Python 3.11.15
workshop exec hermes -- bash -lc 'hermes doctor'
# ✓ Python 3.11.15 / ✓ Virtual environment active
# ✓ OpenAI SDK / Rich / python-dotenv / PyYAML / HTTPX
# ✓ ~/.hermes/.env file exists
隔離境界の検証(本企画の核心)
サンドボックスの意味があるかを、実際に確かめる。共有ディレクトリの外にダミーの機密ファイルを置き、中から見えないことを確認する。
# 共有ディレクトリの外(ホストの $HOME 直下)に置く
echo "TOP-SECRET" > ~/host_only_secret.txt
# サンドボックス内からホストの $HOME を覗こうとする
workshop exec hermes -- bash -lc 'ls /home/toming'
# ls: cannot access '/home/toming': No such file or directory ← 不可視
# 共有ディレクトリだけは見える
workshop exec hermes -- bash -lc 'ls /home/workshop/shared/'
# from_host.txt from_workshop.txt
期待通り、HERMES からホストのファイルシステムは見えず、明示的に共有した ~/hermes-sandbox/shared だけにアクセスできる。自律エージェント + ブラウザ制御 + curl-bash インストールという、本来なら身構える組み合わせを、ホストへの攻撃面を最小限に絞って動かせている。
最終構成
| 項目 | 値 |
|---|---|
| ホスト OS | Ubuntu 24.04.4 LTS |
| バックエンド | LXD 6.8(6/stable。5.21 LTS では不可) |
| Workshop | 0.9.1 |
| プロジェクト | ~/hermes-sandbox(定義は .workshop/) |
| サンドボックス内 | Ubuntu 24.04.4 / user=workshop / HOME=/home/workshop |
| 共有マウント | host ~/hermes-sandbox/shared ↔ workshop /home/workshop/shared(双方向・永続) |
| HERMES | v0.16.0 / Python 3.11.15 / 73 skills / ~/.hermes/ |
| 残作業 | hermes setup で LLM プロバイダ(API キー)を設定 |
再現用クイックスタート
ここまでの試行錯誤を踏まえ、最短手順を一枚にまとめる。
# 1. 前提(最初から LXD 6 系で入れるのが正解)
sudo snap install workshop --classic
sudo snap install lxd --channel=6/stable # 6.8+ 必須。デフォルトの 5.21 LTS では動かない
sudo lxd init --minimal
sudo snap restart workshop
# 2. Docker 併用ホストなら、ブリッジ転送を許可(github 等への接続ハング対策)
sudo nft insert rule ip filter DOCKER-USER iifname workshopbr0 accept
sudo nft insert rule ip filter DOCKER-USER oifname workshopbr0 ct state related,established accept
# 3. プロジェクト作成・起動
mkdir -p ~/hermes-sandbox/shared && cd ~/hermes-sandbox
workshop init hermes --sdks uv
workshop launch hermes
# 4. 共有マウント用 SDK を定義(.workshop/shared/sdk.yaml)
# name: shared / plugs.shared.interface: mount / workshop-target: /home/workshop/shared
# hermes.yaml の sdks に - name: project-shared を追記
workshop refresh hermes
workshop stop hermes
workshop remount hermes/shared:shared ~/hermes-sandbox/shared
workshop start hermes
# 5. HERMES をサンドボックス内にインストール
workshop exec hermes -- bash -lc \
'curl -fsSL https://raw.githubusercontent.com/NousResearch/hermes-agent/main/scripts/install.sh | bash'
workshop exec hermes -- bash -lc 'hermes doctor'
# 6. LLM 設定(対話)。サンドボックスに入って setup を実行
workshop shell hermes
# (中で) hermes setup
おわりに
Workshop は「devcontainer の LXD ネイティブ版」と言える位置づけで、特に自律 AI エージェントのサンドボックスとして相性が良い。今回得られた実践的な勘所は次の通り。
- LXD は最初から
6/stable。LTS(5.21)では Workshop が動かない。 - ファイル共有は mount プラグ。
sketch-sdk→eject→remountの流れを押さえれば、任意ディレクトリを双方向共有できる。非空ディレクトリへの remount は停止が必要。 - Docker 併用ホストではブリッジ転送に注意。
workshop warningsの指示通りDOCKER-USERに nft ルールを足す。症状が「特定ホストだけハング」なので原因に気づきにくい。 - 結果として、ホストの $HOME を一切見せずに、自律エージェントを安全に走らせ、必要な成果物だけを1ディレクトリで受け渡せる構成が完成した。
次の一歩は hermes setup で LLM プロバイダ(Nous Portal / OpenRouter など)を設定すること。隔離は済んでいるので、ここから先は安心してエージェントに遊んでもらえる。
検証日: 2026-06-11 / Ubuntu 24.04.4 LTS