Headmesh Agent Sandbox について
Headmesh Agent Sandbox は、実ファイルや依存関係、Git、プライベートサービスを扱うエージェント向けの永続 Docker サンドボックスです。Go デーモンとホスト CLI により、MCP アクセス、永続セッション、通常モードと YOLO モードを提供します。
主な機能
- 01永続 Docker サンドボックス
- 02コマンド実行とファイル編集
- 03Git と tmux セッション管理
- 04スコープ付きトークンと監査
- 05通常モードと YOLO モード
活用シーン
- 01コーディングエージェントで実際のプロジェクトファイルを操作
- 02再接続後もエージェントセッションを維持
- 03サンドボックスのツールを MCP クライアントに公開
MCP 対応
`headmesh-sandboxd` デーモンは、コマンド実行、セッション操作、ファイルシステム操作などのサンドボックスツールを MCP 経由で公開します。Unix ソケットと HTTP に対応し、HTTP では bearer トークン認証を使用します。
設定
リモートサーバーはインストール不要です。クライアントを選んで設定をコピーするか、ワンクリックで導入できます。
https://{sandbox_host}/mcpclaude mcp add --transport http headmesh-dev-headmesh-sandbox https://{sandbox_host}/mcp{"mcpServers":{"headmesh-dev-headmesh-sandbox":{"type":"http","url":"https://{sandbox_host}/mcp"}}}Cursor で開く{"servers":{"headmesh-dev-headmesh-sandbox":{"type":"http","url":"https://{sandbox_host}/mcp"}}}VS Code で開く{"mcpServers":{"headmesh-dev-headmesh-sandbox":{"type":"http","url":"https://{sandbox_host}/mcp"}}}{"mcpServers":{"headmesh-dev-headmesh-sandbox":{"serverUrl":"https://{sandbox_host}/mcp"}}}コマンドはターミナルで実行し、JSON は各クライアントの MCP 設定に貼り付けて再起動します。認可が必要なサーバーは初回接続時にログインを求めます。
https://{sandbox_host}/mcp認証が必要ツール
詳細
Headmesh エージェント用サンドボックス
ASUS 上で稼働し、Aaryn の Headscale テールネットに独自のタグ付きノードとして参加する、永続的な Docker コンテナです。クラウドエージェントは、実際のファイル、実際の依存関係、プライベートサービスを利用できます。エージェントは、次の複数の入口からアクセスします。
- MCP サーバー
headmesh-sandboxd; - Claude のセルフホスト型ランナーと Claude Remote Control;
- Tailscale SSH 経由の Codex;
- OpenAI Secure MCP Tunnel;
- ホストから Agents API を通じて制御する
codex exec-server。
計画書の §6 と §7(docs/plan.md)を実装しています。
| パス | 内容 |
|---|---|
image/ | ubuntu:26.04(ダイジェストで固定)をベースにしたマルチステージ Dockerfile、s6-overlay サービス、git フック、ガイドラインのテンプレート、docker-bake.hcl、container-structure-test、hadolint の設定 |
compose/ | regular プロファイルと yolo プロファイルを備えた compose.yaml(CLI に埋め込み) |
config/ | sandbox.example.toml とその JSON Schema(sandbox.schema.json) |
sandboxd/ | MCP デーモン:ツール、セッション(tmux)、認証、監査、ステータス |
cmd/headmesh-sandboxd/ | デーモンのバイナリと、コンテナ内ヘルパー(hm) |
cmd/headmesh-sandbox/ | ホスト CLI |
host/ | イグレスファイアウォールと restic バックアップのユニット。Aaryn が sudo を使ってインストールします。host/README.md を参照してください |
tests/acceptance/ | 5 項目のチェック用ハーネス、再作成の追加チェック、prompt.md |
tests/e2e/ | 稼働中のコンテナに対するライブテスト |
docs/ | container-contract.md(イメージ、Compose、CLI、デーモン間のインターフェース)と ADR |
これは独立した Go モジュール github.com/headmesh-dev/headmesh/sandbox であり、ルートの go.work に含まれています。ここで go test -race ./... を実行してください。
検証済みの内容(2026-10-27)
以下はすべて、テールネットに参加せずにローカルで実行しました。
go test -race ./...が成功します。実際の tmux と git を使った統合テスト、およびプロセス内の sandboxd に対するハーネスも含まれます。リポジトリの設定でgolangci-lint runを実行した結果、問題は 0 件です。docker buildx bake -f sandbox/image/docker-bake.hcl sandbox-localによりheadmesh-sandbox:dev(2.39 GB)がビルドされます。hadolint の指摘はなく、container-structure-test は 43/43 件成功します。headmesh-sandbox upは §6.3 の強化設定で Compose 経由でコンテナを起動します。tailscaled がログアウト状態でも正常と報告され、未設定の入口は待機状態になり、その理由を示します。statusとdoctorは動作します。- 稼働中のコンテナに対して、
tools/list(31 個のツール)、exec、session_*、fs_*は Unix ソケット経由と bearer token を使った HTTP 経由の両方で成功します。プロトコルの 2 つのバージョン、すなわちステートレスな 2026-07-28 と、ハンドシェイクを行う 2025-11-25 がどちらも動作します(tests/e2e)。 - 受け入れテストのルート
mcpとルートssh(コンテナ内で実行。フェーズ 1 の後、新しいプロセスからフェーズ 2 を実行):- チェック 1~3 は成功します:uid 1000、Ubuntu 26.04、ファイルのハッシュがホスト側のコピーと一致し、依存関係が実行されます。
- チェック 5 の動作は成功します:再接続後もセッションが実行中で、マーカーが存在します。
- ノードがまだ参加していないため、チェック 4 と 5 は引き続き失敗します。
acceptance recreateは 5/5 件成功します:home と~/.bash_historyが保持され、起動時の ID が変わり、古いセッションはlost (container restarted at …)と報告されます。テールネット ID の項目は参加していないためスキップされました。- YOLO:
- 新しいバックアップ記録がなければ拒否されます。
- バックアップ記録があれば、
/hostをサブツリーごとに読み取り専用にして起動します。root でも書き込みは失敗し、伝播方式はprivate、/etc/shadowの読み取り結果は 0 バイト、~/.sshはマスクされ、ホストの tailscaled の状態と Docker のデータルートは含まれません。 - TTL に達すると監査エントリを記録して自動停止し、次回の
upは Regular になります。
スパイクの結果は docs/adr/spike-t0{1,2,3,7,8}-*.md と docs/adr/spike-t16-*.md に記載されています。その他の実装上の決定事項は docs/adr/0001-sandbox-implementation-decisions.md に記載されています。
対応が必要なセキュリティ上の指摘
- 登録前に送信トラフィック用ファイアウォールをインストールしてください(
host/firewall、以下の手順 1)。Docker ブリッジコンテナは、そうしないとホストが到達できるあらゆる場所にアクセスできます。LAN、自身のサービス、さらにホスト自体が tailnet ノードの場合はその tailnet も対象です。ホスト固有の指摘事項は内部運用メモに記載されています。 - YOLO からはホストが読み取り専用で見えます。ファイル権限が許す範囲で、データディレクトリや証明書も含まれます。エージェントは uid 1000 で実行されるため、root のみがアクセスできるファイルは読み取れませんが、コンテナ内の tailscaled と runner は root で実行されます。機密情報はすべて
[mounts] extra_denylistに追加してください。拒否リストに登録したサブツリーは/hostに含まれません。 - サンドボックス内では
UV_PROJECT_ENVIRONMENT=.venv-sandboxが設定されるため、uvがプロジェクトのホスト側.venvをコンテナのパスに対して再構築することはありません。各プロジェクトの.gitignoreに.venv-sandbox(または.venv*)が含まれていることを確認してください。
クイックスタート(ホスト)
cd /mnt/storage/projects/headmesh/sandbox
go build -o ~/.local/bin/headmesh-sandbox ./cmd/headmesh-sandbox
docker buildx bake -f image/docker-bake.hcl sandbox-local # headmesh-sandbox:dev
headmesh-sandbox init # /mnt/storage/headmesh/sandbox
$EDITOR /mnt/storage/headmesh/sandbox/config/sandbox.toml # image = "headmesh-sandbox:dev" until GHCR has a release
headmesh-sandbox scan-secrets # review, then --apply
headmesh-sandbox token create claude-code-mac > ~/.config/headmesh/sandbox-token
headmesh-sandbox render --diff
headmesh-sandbox up
headmesh-sandbox status
headmesh-sandbox doctor
その他のコマンド:down [--yolo|--all]、recreate、logs [service|audit] [-f]、shell [--root]、attach(tmux でエージェントを監視)、backup、token list|revoke、acceptance run|verify|report|recreate、および agent run --provider openai。
シークレットは /mnt/storage/headmesh/sandbox/secrets/<name> に、モード 0600 で保存します。ファイルが空の場合、その入口は無効です。シークレットはマウントされません。CLI は環境変数を通じて docker compose に渡し、Compose がそれぞれをコンテナ内に root:root 0400 としてコピーします(スパイク T-1)。
Aaryn の対応が必要な手順
各手順には sudo、本番環境への変更、またはアカウントが必要です。いずれもまだ実施されていません。
1. ホスト(sudo)
- 送信トラフィック用ファイアウォールとホームバックアップユニットをインストールし、初回バックアップを実行します。正確なコマンドは
host/README.mdに記載されています。headmesh-sandbox doctorで結果を確認してください。egress firewall ✓とhome backup ✓と表示されるはずです。 - restic をインストールします(計画 §5.3)。スリープターゲットはすでにマスクされています(
doctorが確認します)。
2. Headscale ポリシー(本番 ACL の変更;メンテナンス時間帯)
計画 §5.2 と §6.6 を踏まえた手順です。Xcelsior リポジトリの api/infra/headscale/policy.hujson にタグと許可ルールを追加します。最初に headscale policy check を実行してください。ポリシーの tests がすべて成功する必要があります。
"tagOwners": { "tag:sandbox": ["group:admin"], "tag:sandbox-yolo": ["group:admin"] },
"grants": [
// Regular: only the private services you list (keep sandbox.toml [mode.regular] reach in sync).
{ "src": ["tag:sandbox"], "dst": ["tower-server"], "ip": ["tcp:11434"] },
{ "src": ["tag:sandbox-yolo"], "dst": ["*"], "ip": ["*"] },
],
"ssh": [
{ "action": "accept", "src": ["xcelsior@"], "dst": ["tag:sandbox", "tag:sandbox-yolo"], "users": ["agent"] },
],
xcelsior@ は Aaryn 自身のデバイスのみを指す必要があります。プロバイダーホストにタグが付与されれば、これは保証されます(§5.2)。
3. ノードを登録する
# On prod-hetzner: a single-use pre-auth key that carries the tag (Headscale 0.29.6).
sudo headscale preauthkeys create --user <owner> --tags tag:sandbox --expiration 1h
# On the ASUS: store it and recreate.
install -m 600 /dev/stdin /mnt/storage/headmesh/sandbox/secrets/ts_authkey # paste, then Ctrl-D
headmesh-sandbox recreate
headmesh-sandbox status # tailnet Running, node asus-sandbox, dns-check ok
- キーだけでタグが設定されます。タグ付きキーでは Headscale に拒否されるため、
--advertise-tagsは渡しません(スパイク T-8)。代わりにtailscale-upが登録済みタグをTS_TAGSと照合します。 - 状態が存在しない間だけ、
tailscale upはキーを使って実行されます。そのため、以降の再起動で再認証が行われることはありません。タグ付きノードは有効期限がありません。登録後はキーファイルを消去してください。 - YOLO は独立したノード(
asus-sandbox-yolo、tag:sandbox-yolo)で、専用の状態ディレクトリを使用します。初回のup --yolo時に、tag:sandbox-yolo キーを使って同じ方法で登録してください。
4. エントリーポイント
テイルネット経由の MCP(Claude Code、任意の MCP クライアント):
headmesh-sandbox token create claude-code-mac # prints hms_… once
claude mcp add --transport http sandbox http://asus-sandbox.mesh.xcelsior.ca:8780/mcp \
--header "Authorization: Bearer hms_…"
トークンにはスコープ(sandbox:read|exec|write|admin)が設定され、取り消すことができます。
headmesh-sandbox token revoke claude-code-mac を実行すると、数秒以内に反映されます。
OAuth(RFC 9728 のメタデータはすでに
/.well-known/oauth-protected-resource/mcp で提供されています)は、M3 で Headmesh
認可サーバーとともに導入されます。
Claude のセルフホストランナー(plan §7.1):
- claude.ai の管理者設定で「Allow self-hosted environments」をオンにします。
- 環境シークレットを
secrets/claude_env_secretに保存し、secrets/github_tokenにきめ細かな PAT を入力します。 [entrypoints.claude_runner] enabled = trueを設定し、headmesh-sandbox upを実行します。
ランナーは root として実行されます。各セッションは claude-session を経由し、
setpriv で agent に切り替わるため、セッションからシークレットを読み取ることはできません(spike T-1)。
チェックアウトフックは [repos] でマッピングされたリポジトリを、main 上の共有作業コピーにリンクします。
ランナーがセッションの環境に何を設定するかについては、実機での確認がまだ保留中です。
Claude Remote Control(plan §7.2):
[entrypoints.claude_rc] enabled = trueを設定し、headmesh-sandbox upを実行します。headmesh-sandbox shellを実行してから、claude auth login(claude.ai に完全ログイン)を実行し、/home/agentを信頼して、claude remote-controlを一度実行して 「Enable Remote Control? y」に回答します。headmesh-sandbox recreateを実行します。
DISABLE_TELEMETRY、DO_NOT_TRACK、
CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC、DISABLE_GROWTHBOOK は設定しないでください。いずれかが設定されていると Remote
Control は起動しません(spike T-2)。このログイン後、TTY がなくても自動で再起動するかどうかは、まだ確認が必要です。
Tailscale SSH 経由の Codex(plan §7.3):
- 手順 2 の
sshルールが必要です。 - Codex デスクトップで、リモートホスト
[email protected]を追加します。 - スモークテスト:
ssh [email protected] codex --version。 - Tailscale SSH はコンテナ内で動作します(spike T-3)。Codex デスクトップ自体は未テストです。
OpenAI Secure MCP Tunnel(plan §7.4):
- Platform で Tunnel と Read+Use のみを許可するランタイムキーを作成します。キーを
secrets/openai_tunnel_keyに、ID を[entrypoints.openai_tunnel] tunnel_idに設定します。enabled = trueを設定してから、upを実行します。 - Tunnel ルート用のトークンを作成します:
headmesh-sandbox token create chatgpt-tunnel --route tunnel(実行時間 ≤ 110 秒、 レスポンス ≤ 8 MiB)。 - ChatGPT で、Plugins → Add custom MCP server → Connection: Tunnel を選び、上記のトークンを使ってベアラー認証を設定します。
Agents API 経由の Codex exec-server(plan §7.6):
-
アプリケーションキーはサンドボックスの外に保存します。CLI はあなたのユーザーとして実行されるため、
secrets/openai_app_keyを使用します(0600。マウントされず、YOLO の/hostからも除外されています)。読み取り可能であれば、/etc/headmesh/openai-app.keyも使用できます。 -
環境キー(Agents タブで、その他すべての権限は None)を
secrets/openai_env_keyに保存します。 -
次を実行します:
headmesh-sandbox agent run --provider openai --repo xcelsior \ --prompt-file sandbox/tests/acceptance/prompt.md
エグゼキューターはリポジトリ内で agent として実行され、
--linux-sandbox-pid-namespace inherit を指定し、コンテナ内ではフルアクセスを持ちます。Codex の bubblewrap サンドボックスは、コンテナの seccomp および AppArmor プロファイルの下では実行できないため、境界となるのはコンテナです(spike T-16)。環境キーは stdin 経由でエグゼキューターに渡され、トランスクリプトは logs/agent-runs/<session>.jsonl に出力されます。
5. 受け入れ確認(plan §6.15)
headmesh-sandbox acceptance run --route mcp \
--url http://asus-sandbox.mesh.xcelsior.ca:8780/mcp --token-file ~/.config/headmesh/sandbox-token
ssh [email protected] headmesh-sandbox acceptance run --route ssh --phase 1
ssh [email protected] headmesh-sandbox acceptance run --route ssh --phase 2
# runner, exec-server, tunnel: the agent follows tests/acceptance/prompt.md
headmesh-sandbox acceptance recreate
headmesh-sandbox acceptance verify
headmesh-sandbox acceptance report --out ../docs/acceptance/$(date +%F).md
sandbox.toml [acceptance] でターゲットを設定します:
allowed_targetはtag:sandboxに許可する必要があります。denied_targetはブロックする必要があります。このプローブはファイアウォールのインストール後にのみ実行してください。
チェック 4 で有効なのは MagicDNS 名に対するテイルネットアドレスのみです。同じ名前のパブリック DNS レコードは対象になりません。
6. YOLO
-
専用のデプロイキーを作成し、GitHub に「headmesh-sandbox-yolo (asus)」として追加して、
known_hostsを固定します:ssh-keygen -t ed25519 -f /mnt/storage/headmesh/sandbox/secrets/github_ssh_key \ -C "headmesh-sandbox-yolo (asus)"これは YOLO コンテナにのみ渡されます。
-
headmesh-sandbox backupを実行してから、headmesh-sandbox up --yolo --ttl 4hを実行します。
CLI は backup_max_age より新しいバックアップがないと実行を拒否し、監視中であることの確認を求めます。
7. イメージを公開する
sandbox-v0.1.0 タグをプッシュします。.github/workflows/sandbox-image.yml がイメージをビルドし、 hadolint、container-structure-test、trivy を実行して、ghcr.io/headmesh-dev/sandbox にプッシュし、 cosign で署名します。その後、image = "ghcr.io/headmesh-dev/sandbox:0.1.0@sha256:…"` を設定します。
トラブルシューティング
- まず
headmesh-sandbox doctorを実行してください。すべての失敗に修正方法が示されます。 headmesh-sandbox logs sandboxd|tailscaled|tailscale-up|claude-runner|claude-rc|openai-tunnel|audit。- エージェントをリアルタイムで監視するには、
headmesh-sandbox attachを実行します。 - 「IP では接続できるが、名前では接続できない」場合:
dns-checkが修正方法を説明します(落とし穴 3)。
コメント
AI アプリケーションが外部のツールやデータソースに統一的な方法で接続するためのオープンなプロトコルです。クライアントはモデルとの対話を担い、MCP サーバーはツール・リソース・プロンプトテンプレートを提供します。標準メッセージでやり取りするため、1 つのサーバーを複数のクライアントで再利用できます。