はじめに
HX370(CachyOS)上でローカルLLMをいくつも動かしてきたが、今回は「自己スキャフォールディング型」という触れ込みのエージェント特化モデル Ornith-1.0 と、自律実行クライアント Hermes Agent を組み合わせて動かしてみた。単に動かすだけでなく、エージェントにファイル操作・コード実行・ブラウザ操作を任せる以上、安全に隔離した状態で使うことを最初から意識して構成を組んだので、その一連の流れを記録しておく。
この構成は今後もいろいろ試していきたいので、「Hermesシリーズ」として何回かに分けて書いていく予定。今回はその第1回として、導入からコンテナで動くところまでをまとめる。
何を組み合わせたか
- Ornith-1.0: Gemma 4 / Qwen 3.5 をベースにRLで再学習された、エージェント用途特化のOSSモデル(MIT license)。9B/31B/35B MoE/397B MoEのサイズ展開があり、タスクを受け取るとまず自分でPythonの実行ハーネスを書き、それを使ってタスクを解く「self-scaffolding」という設計が特徴。
- Hermes Agent: Nous Researchによる自律エージェントクライアント。モデル本体は持たず、OpenAI互換のどんなエンドポイントにも接続できる。スキル・記憶を蓄積しながら学習していく「学習ループ」を軸に据えている。
つまり構成としては「Ornithをlama.cppでローカルサーブし、Hermes Agentがそこに接続してタスクを実行する」という形になる。
環境
- CPU: Ryzen AI 9 HX370 (Radeon 890M iGPU)
- OS: CachyOS (linux-cachyos kernel)
- 推論バックエンド: Vulkan / llama.cpp
- モデル:
ornith-1.0-35b-Q4_K_M.gguf(35B MoE, Q4_K_M量子化, 約21GB)
既存でQwen3.5-35B-A3B(11434)、LFM2-24B-A2B(11435)、GLM-4.7-Flash(11436)をsystemdサービスとして動かしている環境なので、そこに新しくOrnithを追加する形になる。
Ornithをlama.cppでサーブする
llama-server -m /home/hideaki/src/llama.cpp/models/ornith-1.0-35b-Q4_K_M.gguf \
--host 0.0.0.0 \
--port 11434 \
-c 131072 \
--jinja
いくつかハマったポイントがあるので共有しておく。
--jinjaは必須
llama.cppでツール呼び出し(tool-calling)を機能させるには--jinjaフラグが必要。これを忘れるとHermes側からのfunction callingがそもそも動かない。
--host 0.0.0.0にしないとコンテナから届かない
Hermes Agent自体をDockerコンテナで動かす予定だったので、コンテナからホストのllama-serverに接続する必要がある。しかしllama-serverはデフォルトで127.0.0.1のみをlistenするため、コンテナ側のhost.docker.internal経由の接続がそもそも届かない。--host 0.0.0.0を明示することで解決した。
ただし0.0.0.0でlistenすると同一LAN内の他端末からもアクセスできてしまうため、ufw/nftablesでDockerブリッジのサブネット(172.17.0.0/16)以外からのアクセスを遮断しておくのが安全。
sudo ufw allow from 172.17.0.0/16 to any port 11434 proto tcp
コンテキスト長の要件に注意
Hermes Agentは「64,000トークン未満のコンテキストを持つモデルは起動時に拒否する」という制約がある。デフォルトのままだとllama.cppは32768(32K)でlistenしてしまい、この最低要件を満たさない。-cオプションで明示的に引き上げる必要がある(Ornithの学習時コンテキストは256Kまであるので、メモリと相談しつつ128K程度まで上げた)。
DockerでHermes Agentを動かす
なぜDockerか
Ornithの最大の特徴である「self-scaffolding」は、裏を返せばモデルが自分でPythonコードを書いて実行するということでもある。ホストに直接その権限を与えるのは避けたいので、コンテナに閉じ込めて動かすことにした。
Hermes公式のDockerイメージは都合よく、コアの実行ファイル(/opt/hermes)がroot所有・読み取り専用で、書き込み可能なのは/opt/data(ホストの~/.hermesにマウント)だけという設計になっている。つまり「ディレクトリを絞る」という要件がイメージの構造自体で半分満たされている。
つまずいた点: Dockerのネットワーキングが壊れていた
セットアップ中、コンテナ起動時に以下のエラーに遭遇した。
failed to set up container networking: failed to create endpoint ... on network bridge:
failed to add the host (vethXXXXX) <=> sandbox (vethYYYYY) pair interfaces: operation not supported
原因を辿ると、pacman -Syuでカーネル(linux-cachyos)が更新された直後にまだ再起動しておらず、稼働中のカーネル(7.0.12-1-cachyos)に対応するモジュールディレクトリが/lib/modules/からすでに削除されていた、というオチだった。veth・bridge・br_netfilterのいずれもロードできず、Dockerのbridgeネットワーク作成(veth pair作成)が失敗していた。
# 稼働中カーネルとディスク上のモジュールが食い違っていないか確認
uname -r
ls /lib/modules/
再起動一発で解決。CachyOSはローリングリリースでカーネル更新が頻繁なので、「Dockerが急に動かなくなったら、まずカーネル更新後に再起動を忘れていないか確認する」というのは今後の教訓にしたい。
データディレクトリを作ってセットアップウィザードを実行
mkdir -p ~/.hermes
docker run -it --rm \
--add-host=host.docker.internal:host-gateway \
-v ~/.hermes:/opt/data \
nousresearch/hermes-agent setup
LinuxのDockerではhost.docker.internalがデフォルトで名前解決されないため、--add-host=host.docker.internal:host-gatewayを明示的に付ける必要がある。
セットアップウィザードでの選択
対話式のウィザードが進み、以下を選択した。
- セットアップモード:
Full setup(Nous Portalのクラウドではなく、自前のOrnithエンドポイントに接続したいため) - プロバイダ:
Custom endpoint (enter URL manually) - Base URL:
http://host.docker.internal:11434/v1 - API互換モード:
Chat Completions(llama.cppの標準的な/chat/completions実装に合わせて明示指定) - モデル名:
curl http://localhost:11434/v1/modelsで確認した実際のid(今回はファイルパスそのものだった) - コンテキスト長:
131072(実際にllama-serverへ指定した値と一致させる) - ツール構成: Web検索・TTS・画像生成・Cron・Computer Useはオフ、ブラウザ・ファイル操作・コード実行・メモリ等は有効
- ブラウザプロバイダ:
Local Browser(無料・APIキー不要のヘッドレスChromium、コンテナ内で完結) - terminal.backend:
Localのまま
最後の「terminal.backend」の選択は少し引っかかったポイントなので補足しておく。
terminal.backendはDockerではなくLocalを選ぶ
一見「Dockerで隔離したい」ならterminal.backend: Dockerを選びたくなるが、これは避けた。Hermes自体を既にDockerコンテナの中で動かしている状態でさらにDockerを選ぶと、コンテナの中からさらに別のコンテナを起動する、いわゆるDocker-in-Docker構成になってしまう。これを実現するにはdocker.sockをコンテナにマウントする必要があり、それは実質的にそのコンテナへホスト相当の権限を渡すことを意味する。これまで積み上げてきた「権限を絞る」という方針と矛盾してしまうため、Localのままにした。Hermes自体がコンテナの中にいる以上、外側のコンテナが既に隔離層として機能している。
動作確認
docker run -it --rm \
--add-host=host.docker.internal:host-gateway \
-v ~/.hermes:/opt/data \
nousresearch/hermes-agent
起動すると、モデル名(ornith-1.0-35b-Q4_K_M)、26個のツール、68個のスキルが認識された状態でCLIが立ち上がった。簡単な会話とブラウザ操作タスクを試したところ、問題なく動作した。
⚕ ornith-1.0-35b-Q4_K_M │ ctx -- │ [░░░░░░░░░░] -- │ 2s │ ⏲ 0s
今後の課題
- 起動時に
tirith security scanner enabled but not available — command scanning will use pattern matching onlyという警告が出ている。危険コマンドの検知が単純なパターンマッチングのみにフォールバックしている状態なので、原因を追って本来のスキャナを有効化したい。 - 監査ログ(
audit_log_enabled)をまだ有効化していないので、次に触るタイミングで設定する。 - 実際のChromeブラウザ(ログイン済みセッション)への接続や、Google Workspace連携も試してみたいが、認証情報の権限を絞った専用アカウントを用意してから進める予定。
途中で踏んだDockerネットワーキング障害の原因が「カーネル更新後の再起動忘れ」だったのは若干拍子抜けだったが、ローリングリリース環境では起こりがちな話。同じ轍を踏む人の参考になれば。次回はセキュリティスキャナ周りを掘っていく予定。