DGX Spark に NemoHermes を常駐させて Slack から呼べるチームアシスタントを作る

DGX Spark に NemoHermes を常駐させて Slack から呼べるチームアシスタントを作る

Slack から呼び出せる常駐 AI エージェントを DGX Spark に構築しました。NemoClaw と NemoHermes を使って、セキュアな sandbox 環境でエージェントを動かし、ネットワークポリシーで細かく制御するところまでを試してみます。
2026.08.20

はじめに

こんにちは、クラスメソッド製造ビジネステクノロジー部の嶋田です。

チームで使っている DGX Spark に、Slack から呼び出せる常駐 AI エージェントを構築しました。
最終的には LLM ルーターでローカルとクラウドのモデルを自動で使い分け、Backlog 連携とチーム RAG まで載せています。

本シリーズは全 3 本です。

  1. NemoClaw/NemoHermes で Slack 常駐エージェントを構築する(本記事)
  2. NeMo Switchyard でローカル Nemotron 3.5 Lightning と Fireworks 上の Nemotron 3 Ultra を自動ルーティングする
  3. skill でエージェントに Backlog とチーム RAG を接続する

最終的な全体構成は次のとおりです。

本記事ではこのうち Slack と NemoHermes のあいだ、つまりエージェントを DGX Spark に常駐させて Slack から安全に呼び出せるようにするところまでを扱います。

なお、本記事の執筆時点で NemoClaw は v0.0.109 です。
今後のアップデートで挙動や設定が変わる可能性があります。
最新の情報は公式ドキュメントを確認してください。

NemoClaw と NemoHermes

NemoClaw は NVIDIA が公開しているエージェント実行基盤です。
エージェント本体(OpenClaw、Hermes、Deep Agents から選択)を sandbox と呼ばれる隔離実行環境で動かします。
sandbox は Docker コンテナ、Landlock、L7 の egress ポリシーで構成されていて、エージェントがどのホストのどのパスにどのメソッドでアクセスできるかまでをホワイトリスト方式で宣言的に絞れます。

NemoHermes は Hermes Agent(Nous Research 製)を NemoClaw に載せた構成の呼び名です。
Slack、Discord、Telegram などのメッセージングチャネルが標準搭載されているため、Slack ゲートウェイを自作する必要はありません。

社内データを触るエージェントを常駐させるにあたって、この sandbox の設計は安心材料になります。
第 3 回で扱う Backlog 連携では、課題の作成や更新は許すが削除は通さない、という粒度をネットワークポリシーで固定しました。

前提環境

  • DGX Spark(GB10、統合メモリ 128GB、Ubuntu 24.04 ARM)
  • Docker(プリインストール済み。ユーザーを docker グループに追加しておく)
  • Node.js 22(プリインストールは v18 だったので nvm でユーザー空間に導入)
sudo usermod -aG docker $USER   # 要再ログイン
curl -fsSL https://raw.githubusercontent.com/nvm-sh/nvm/v0.40.3/install.sh | bash
nvm install 22

NemoClaw のインストール

公式の headless server デプロイ手順に従います。

インストール元はコミット SHA でピン留めします。
latest などの可変タグを使わないことが公式に推奨されています。

モデルは OpenAI 互換エンドポイントを custom provider として指定できます。
最初の疎通確認ではプリインストールの Ollama に載せたモデルへ直結し、ルーター(Switchyard)は第 2 回で差し込みます。

export NEMOCLAW_INSTALL_REF="d027b770d7130685d020964c4811211baf3d5f31"  # v0.0.109
export NEMOCLAW_AGENT=hermes
export NEMOCLAW_SANDBOX_NAME=team-assistant
export NEMOCLAW_POLICY_TIER=balanced
export NEMOCLAW_PROVIDER=custom
export NEMOCLAW_ENDPOINT_URL="http://localhost:11434/v1"   # Ollama
export NEMOCLAW_MODEL="qwen3.6:35b"
export NEMOCLAW_COMPATIBLE_AUTH_MODE=none

curl -fsSL "https://raw.githubusercontent.com/NVIDIA/NemoClaw/${NEMOCLAW_INSTALL_REF}/install.sh" | \
  NEMOCLAW_INSTALL_REF="$NEMOCLAW_INSTALL_REF" \
  NEMOCLAW_NON_INTERACTIVE=1 \
  NEMOCLAW_ACCEPT_THIRD_PARTY_SOFTWARE=1 \
  bash

6 分ほどで sandbox が起動しました。
OpenAI 互換 API(127.0.0.1:8642)も生えるので、CLI から疎通確認できます。

TOKEN=$(nemohermes team-assistant gateway-token --quiet)
curl -s -H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" \
  http://127.0.0.1:8642/v1/chat/completions \
  -d '{"model":"hermes","messages":[{"role":"user","content":"一言で自己紹介して"}]}'
{
  "id": "chatcmpl-69ed2d…",
  "object": "chat.completion",
  "created": 1787206201,
  "model": "hermes",
  "choices": [
    {
      "index": 0,
      "message": {
        "role": "assistant",
        "content": "私はNVIDIA OpenShellサンドボックス内で動作するHermes Agent(Nous Research製)です。コード実行、検索、分析、クリエイティブ作業などをツール経由で支援します。"
      },
      "finish_reason": "stop"
    }
  ],
  "usage": { "prompt_tokens": 16548, "completion_tokens": 86, "total_tokens": 16634 }
}

日本語で返ってきました。
自分が sandbox の中にいることも認識しています。

usage に注目してみます。「一言で自己紹介して」と送っただけで prompt_tokens が 16,548 です。
Hermes はツール定義や振る舞いの指示を system prompt に積んでいるため、会話が始まる前の時点でこれだけ消費します。
コンテキストの見積もりをするときは、この定数分を差し引いて考える必要があります。

nemohermes team-assistant status を実行すると、sandbox に適用されているポリシーの実物を確認できます。
推論は inference.local という仮想ホスト経由で POST /v1/chat/completions など特定のパスのみ許可、curl バイナリは pypi と brew のエンドポイント限定、といった具合に L7 で絞られています。

Slack 連携

ここからは Slack からエージェントにアクセスするための手順を書き出します。

app manifest の生成

Slack app に必要な OAuth scope を調べて回る必要はありません。
sandbox 内の Hermes CLI が Slack app manifest を生成してくれます。

C=$(docker ps --format '{{.Names}}' | grep team-assistant)
docker exec "$C" hermes slack manifest > slack-manifest.json

Socket Mode 有効、bot scope 16 個、イベント購読まで揃った manifest が出てくるので、api.slack.com/apps の「Create New App」から「From a manifest」に貼り付けます。

1 点注意があります。
生成された manifest には slash command が 50 個含まれています(/new/stop/model など)。
slash command はワークスペース全体で名前空間を共有します。社内ワークスペースで slash command の候補が多数表示されて煩雑になることを防ぐため、/hermes 1 個に絞って登録しました。

この絞り込みには副作用があります。
Hermes は自分の案内やバナーで「セッションを切るには /new」と表示してきますが、登録していないので動きません。
/hermes new のように /hermes のサブコマンドとして渡すと、内部で /new に変換されて実行されます。

トークン登録と Socket Mode

app をインストールして Bot Token(xoxb-)と App-Level Token(xapp-connections:write)を取得したら、sandbox に登録します。

nemohermes team-assistant channels add slack
# → トークン 2 つを貼り付け、メンバーとチャンネルの allowlist を入力

トークンは auth.testapps.connections.open で検証されてから保存され、sandbox の rebuild が走って Slack 用の egress(slack.com、wss-primary.slack.com など 5 ホストのみ)が開きます。
Socket Mode なので inbound のポート開放は不要です。
社内ネットワークの DGX でもそのまま動きます。

ハマりどころ: rebuild の credential 検証

ここで rebuild が preflight で止まりました。

Rebuild preflight failed: cannot safely reuse the gateway credential for 'compatible-endpoint'.
Export COMPATIBLE_API_KEY to use normal credential validation and upsert.

原因は、最初のインストールで NEMOCLAW_COMPATIBLE_AUTH_MODE=none(認証なしの Ollama)を選んだことです。
rebuild 時に再検証できる credential が存在しないため、安全側に倒して止まる設計になっています。
対処はメッセージの指示どおり、ダミーの API キーを export して rebuild するだけです(Ollama は Authorization ヘッダを無視するので実害はありません)。

COMPATIBLE_API_KEY=ollama-local-noauth nemohermes team-assistant rebuild --yes

認証なしのローカルエンドポイントで onboard すると、以後の rebuild でも毎回この環境変数が必要になります。
最初からダミーキーを credential として登録しておくほうが楽です。

アクセス制御

チームで使うために 2 つ設定を詰めました。

メンバーの allowlist

SLACK_ALLOWED_USERS を空にすると、全開放ではなく手動ペアリング要求のモードになります。
チームチャンネルのメンバー全員に開けたい場合は、メンバー ID をカンマ区切りで列挙します。

allowlist を更新しようと channels add slack を再実行しても、保存済みの値が「already set」として再利用され、プロンプトは出ません。
ソースを読むと、config 値は環境変数が保存値より優先される実装だったので、環境変数を添えて再実行すると更新できます。

SLACK_ALLOWED_USERS="U0XXXXXXXXX,U0YYYYYYYYY,..." \
SLACK_ALLOWED_CHANNELS="C0ZZZZZZZZZ" \
nemohermes team-assistant channels add slack   # トークンは再貼り付けが必要

DM の禁止

チームの共有アシスタントなので、会話はチャンネルに限定して DM は塞ぎます。
これは NemoHermes 側の設定ではなく、Slack app manifest から im:*mpim:* の scope、message.immessage.mpim のイベント購読を削除して再インストールします。
Slack 側でイベント自体が届かなくなります。
あわせて App Home の Messages タブを無効化すると、DM の入力欄も塞がれます。

スレッド内で勝手に喋る問題

運用を始めてしばらくすると、別の困りごとが出ました。
一度 @mention したスレッドでは、以降メンションしていない発言にもボットが反応してきます。
人間同士の雑談が続いているところに割り込んでくるので、チームから止めたいという声が上がりました。

これは仕様です。
Slack アダプタの実装を読むと、mention されたスレッドの thread_ts を記憶しておき、そのスレッドの後続メッセージには mention なしでも応答する作りになっています。
_mentioned_threads という集合に溜め込んでいて、コメントにも「to thread replies even without an explicit @mention」と書かれています。

応答条件は複数あり、mention 済みスレッド以外にも「ボット自身のメッセージへの follow-up」や「セッションが生きているチャンネル」といった自動トリガがあります。
これらをまとめて切るスイッチが用意されていました。

キー 既定 効果
strict_mention false true にすると毎メッセージで @mention 必須。上記の自動トリガをすべて無効化する
require_mention true チャンネルのメッセージに mention を要求する
free_response_channels (なし) 指定したチャンネルでは mention を不要にする

platforms.slack.extra.strict_mention を true にすれば、スレッドを記憶しなくなります。
ただしこの設定の入れ方には落とし穴があり、sandbox 内から設定するとクラッシュを招きます
sandbox 内の設定ファイルを直接書き換えたときに何が起きるかは、後続の記事で取り扱う予定です。

もう一つ注意点があります。
この値は sandbox の rebuild で失われます。
platforms.slack は NemoClaw の manifest から再生成される領域で、rebuild の状態復元の対象にも含まれていないためです。
channels add slack も内部で rebuild を伴うので同じことが起きます。
rebuild したら設定し直す、という運用手順に入れておく必要があります。

動作確認

チャンネルで @Hermes にメンションすると、DGX 上のローカルモデルが応答を返してきます。

Slack 上で DGX Spark 上のローカルモデルからのレスポンスを確認した

この時点で次の状態になりました。

  • チャンネルに参加しているメンバー全員が Slack から呼び出せる
  • DM でのやりとりは不可
  • 呼ばれたときだけ応答し、スレッドに居座らない
  • エージェントは sandbox 内で L7 ポリシーに縛られて動く
  • 推論はすべて DGX 上のローカルモデル

おわりに

NemoClaw を使うと、Slack ゲートウェイを自作せずに、ネットワークポリシーで縛られた常駐エージェントを 1 日かからず立てられました。
つまずいたのは、認証なしのエンドポイントで onboard したせいで rebuild が止まる件、設定の更新に環境変数が優先される作法、そしてスレッドを記憶して mention なしでも喋る仕様の 3 つでした。
どれも実装を読めば理由のある挙動で、当てずっぽうで直そうとすると遠回りになります。

ただしこの時点ではモデルが 1 つだけです。
次回は NeMo Switchyard を挟んで、ローカルの Nemotron 3.5 Lightning(30B)と Fireworks 上の Nemotron 3 Ultra(550B)をリクエスト内容で自動的に振り分けます。

参考資料

この記事をシェアする

関連記事