Skill flagged — suspicious patterns detected

ClawHub Security flagged this skill as suspicious. Review the scan results before using.

Multi-Agent Sandbox

v1.0.0

Setup multi-agent sandbox infrastructure with Docker, Discord, SSH, and Tailscale. Use when: (1) creating a sandboxed agent for cross-gateway collaboration,...

0· 376· 1 versions· 2 current· 2 all-time· Updated 9h ago· MIT-0
byErwan Lee Pesle@superworldsavior

Install

openclaw skills install multi-agent-sandbox

Multi-Agent Sandbox

Set up sandboxed agents that collaborate with agents from other OpenClaw gateways via Discord and a shared VPS, without exposing private data.

Architecture

Gateway A (Server A)                  Gateway B (Server B)
├── Main Agent (full access)          ├── Main Agent (full access)
│   agentToAgent.allow: ["*"]         │   agentToAgent.allow: ["*"]
└── Sandbox Agent (Docker)            └── Sandbox Agent (Docker)
    agentToAgent.allow: ["main"]          agentToAgent.allow: ["main"]
    ├── Discord ←── Shared Server ──→ Discord
    │                requireMention: true
    └── SSH ─→ socat ─→ Tailscale ─→ Shared VPS ←── SSH
                                      100.y.y.y

Three pillars: socat bridges (container → host → VPS), Tailscale mesh VPN (private networking), Discord + sessions_send (inter-agent communication).

Prerequisites

  • OpenClaw running with Docker sandbox support
  • Tailscale installed on all machines (server A + VPS + server B)
  • A Discord bot token per sandbox agent (https://discord.com/developers/applications)
  • A shared VPS accessible via Tailscale

Step 1 — Create the Sandbox Agent

Add to openclaw.json under agents.list:

{
  "id": "sandbox",
  "workspace": "/path/to/workspace-sandbox",
  "model": {
    "primary": "anthropic/claude-sonnet-4-6",
    "fallbacks": ["openai/gpt-4o"]
  },
  "identity": {
    "name": "Sandbox",
    "emoji": "📦"
  },
  "sandbox": {
    "mode": "all",
    "workspaceAccess": "rw",
    "sessionToolsVisibility": "all",
    "scope": "agent",
    "docker": {
      "image": "openclaw-sandbox:bookworm-slim",
      "readOnlyRoot": true,
      "network": "bridge",
      "memory": "1536m",
      "cpus": 2
    },
    "browser": { "enabled": true }
  },
  "tools": {
    "agentToAgent": {
      "allow": ["your-main-agent-id"]
    },
    "alsoAllow": ["message", "sessions_send", "sessions_list", "sessions_history"],
    "deny": ["gateway", "process", "whatsapp_login", "cron"],
    "sandbox": {
      "tools": {
        "allow": [
          "exec", "process", "read", "write", "edit", "apply_patch",
          "image", "web_search", "web_fetch",
          "sessions_list", "sessions_history", "sessions_send", "sessions_spawn",
          "subagents", "session_status", "message", "browser"
        ],
        "deny": [
          "canvas", "nodes", "gateway", "telegram", "irc", "googlechat",
          "slack", "signal", "imessage", "whatsapp_login", "cron"
        ]
      }
    }
  }
}

Key constraints:

  • sandbox.mode: "all" — all exec runs through Docker, never on host
  • readOnlyRoot: true — container filesystem is immutable except workspace
  • tools.deny — no gateway (can't modify config), no cron (can't schedule on host)
  • scope: "agent" — isolated container per agent (valid values: session | agent | shared)

Step 2 — A2A Permissions (Hub-Spoke Pattern)

Configure bidirectional communication using per-agent outbound allowlists (PR #39102):

{
  "tools": {
    "agentToAgent": { "enabled": true, "allow": ["*"] }
  },
  "agents": {
    "list": [
      {
        "id": "main-agent",
        "tools": { "agentToAgent": { "allow": ["*"] } }
      },
      {
        "id": "sandbox",
        "tools": { "agentToAgent": { "allow": ["main-agent"] } }
      }
    ]
  }
}

Result: sandbox → main-agent ✅ | sandbox → other-sandbox ❌ | main-agent → anyone

Both agents also need subagents.allowAgents for sessions_spawn:

// Main agent
"subagents": { "allowAgents": ["sandbox"] }

// Sandbox agent
"subagents": { "allowAgents": ["main-agent"] }

Must be set on BOTH agents. Forgetting one direction = silent "access denied" errors.

Step 3 — Add SSH to Docker Image

The default sandbox image lacks SSH. Edit Dockerfile.sandbox:

RUN apt-get update \
  && apt-get install -y --no-install-recommends \
    bash ca-certificates curl git jq \
    openssh-client \
    python3 ripgrep \
  && rm -rf /var/lib/apt/lists/*

Rebuild and force-recreate containers:

docker build -f Dockerfile.sandbox -t openclaw-sandbox:bookworm-slim .
docker ps --format "{{.ID}} {{.Image}}" | grep sandbox | awk '{print $1}' | xargs -r docker rm -f

Step 4 — Socat Bridges

Two bridges on each host. Always bind on 172.17.0.1 (docker0), never 0.0.0.0.

Bridge 1: Container → Gateway (local)

# /etc/systemd/system/socat-bridge-docker0-gateway.service
[Unit]
Description=Socat bridge: docker0 → Gateway
After=network.target docker.service

[Service]
Type=simple
ExecStart=/usr/bin/socat TCP-LISTEN:18789,bind=172.17.0.1,reuseaddr,fork TCP:127.0.0.1:18789
Restart=always
RestartSec=5

[Install]
WantedBy=multi-user.target

Bridge 2: Container → VPS SSH (via Tailscale)

# /etc/systemd/system/socat-bridge-docker0-vps-ssh.service
[Unit]
Description=Socat bridge: docker0:2222 → VPS Tailscale SSH
After=network.target docker.service tailscaled.service
Wants=tailscaled.service

[Service]
Type=simple
ExecStart=/usr/bin/socat TCP-LISTEN:2222,bind=172.17.0.1,reuseaddr,fork TCP:100.y.y.y:22
Restart=always
RestartSec=5

[Install]
WantedBy=multi-user.target

Enable, start, and open firewall:

sudo systemctl daemon-reload
sudo systemctl enable --now socat-bridge-docker0-gateway socat-bridge-docker0-vps-ssh
sudo ufw allow in on docker0 to 172.17.0.1 port 18789 proto tcp comment "socat-gateway"
sudo ufw allow in on docker0 to 172.17.0.1 port 2222 proto tcp comment "socat-vps-ssh"

The VPS bridge depends on Tailscale (Wants=tailscaled.service). Without this, socat tries to connect before the Tailscale interface exists — silent failure.

Step 5 — Discord Multi-Bot

Create a Discord bot

  1. https://discord.com/developers/applications → New Application
  2. Bot → Reset Token → copy
  3. Enable all 3 Privileged Gateway Intents (MESSAGE CONTENT, SERVER MEMBERS, PRESENCE)
  4. Invite: https://discord.com/oauth2/authorize?client_id=<APP_ID>&permissions=274878024704&scope=bot

Configure in openclaw.json

"discord": {
  "enabled": true,
  "accounts": {
    "default": {
      "enabled": true,
      "name": "Main Bot",
      "token": "$DISCORD_TOKEN_MAIN",
      "groupPolicy": "allowlist",
      "dmPolicy": "allowlist",
      "allowFrom": ["<YOUR_DISCORD_USER_ID>"],
      "guilds": {
        "<PRIVATE_GUILD_ID>": {
          "slug": "private",
          "requireMention": false
        }
      }
    },
    "sandbox": {
      "enabled": true,
      "name": "Sandbox Bot",
      "token": "$DISCORD_TOKEN_SANDBOX",
      "groupPolicy": "allowlist",
      "dmPolicy": "deny",
      "guilds": {
        "<SHARED_GUILD_ID>": {
          "slug": "shared",
          "requireMention": true
        }
      }
    }
  }
}

Agent routing bindings

"mappings": [
  {
    "agentId": "main-agent",
    "match": { "channel": "discord", "accountId": "default", "guildId": "<PRIVATE_GUILD_ID>" }
  },
  {
    "agentId": "sandbox",
    "match": { "channel": "discord", "accountId": "sandbox", "guildId": "<SHARED_GUILD_ID>" }
  }
]

requireMention: true is non-negotiable on shared guilds. Without it, two bots respond to each other = infinite loop + astronomical token bill.

Step 6 — Tailscale

Install on all machines:

curl -fsSL https://tailscale.com/install.sh | sh
sudo tailscale up --ssh

The --ssh flag enables Tailscale SSH (identity-based auth, no keys to manage). For machines where you can't interactively authenticate:

# Generate auth key at https://login.tailscale.com → Settings → Keys
sudo tailscale up --authkey=tskey-auth-xxxxx --ssh

Do NOT install Tailscale inside the container. It requires NET_ADMIN capability, which defeats the sandbox purpose. Use socat bridges instead.

Step 7 — Sandbox Workspace

Create minimal workspace files:

mkdir -p /path/to/workspace-sandbox

SOUL.md — Define agent identity and constraints. TOOLS.md — Document SSH access: ssh -o StrictHostKeyChecking=no root@172.17.0.1 -p 2222

Communication Patterns

# Main → Sandbox (same gateway)
sessions_send(label="sandbox", message="...")

# Sandbox → Main (same gateway)
sessions_send(label="main-agent", message="...")

# Agent A → Agent B (different gateways)
# Only via Discord @mention. No sessions_send across gateways.

# Async collaboration
# Both agents SSH to VPS /workspace and use files.

Gotchas

  1. Container not using new image — After rebuilding Docker image, stop and remove old containers. OpenClaw reuses running containers.
  2. Cross-context messaging — Agent spawned from WhatsApp cannot write to Discord. First trigger must come from the right channel.
  3. MESSAGE CONTENT Intent — Must be enabled in Discord Developer Portal or bot receives empty messages.
  4. Socat silent timeout — If ssh -p 2222 hangs with no error, check UFW rules on docker0.
  5. Agent ID rename — Renaming an agent (e.g., sandboxspoke) breaks active sessions that reference the old ID. Add the old ID to agentToAgent.allow until those sessions expire.
  6. sessions_send timeouttimeoutSeconds: 0 for fire-and-forget, timeoutSeconds: 60 when waiting for a response. Timeout ≠ message not delivered.
  7. Bot token exposure — Never post tokens in Discord channels. If exposed, reset immediately via Developer Portal.

Version tags

latestvk97e3vyn2cv3mx9sdajrjate5h82gwxd