🎁 Get the FREE AI Skills Starter Guide β€” Subscribe β†’
BytesAgainBytesAgain
πŸ¦€ ClawHub

Multi-Agent Sandbox

by @superworldsavior

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

Versionv1.0.0
Downloads699
Installs2
TERMINAL
clawhub install multi-agent-sandbox

πŸ“– About This Skill


name: multi-agent-sandbox description: "Setup multi-agent sandbox infrastructure with Docker, Discord, SSH, and Tailscale. Use when: (1) creating a sandboxed agent for cross-gateway collaboration, (2) setting up Discord multi-bot with separate accounts and requireMention gating, (3) configuring socat bridges for container→VPS SSH via Tailscale, (4) enabling bidirectional agent-to-agent communication via sessions_send with per-agent A2A allowlists, (5) sharing a VPS workspace between agents from different OpenClaw gateways, (6) isolating sandbox agents from main agent private data."

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=&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": [""],
          "guilds": {
            "": {
              "slug": "private",
              "requireMention": false
            }
          }
        },
        "sandbox": {
          "enabled": true,
          "name": "Sandbox Bot",
          "token": "$DISCORD_TOKEN_SANDBOX",
          "groupPolicy": "allowlist",
          "dmPolicy": "deny",
          "guilds": {
            "": {
              "slug": "shared",
              "requireMention": true
            }
          }
        }
      }
    }
    

    Agent routing bindings

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

    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., sandbox β†’ spoke) breaks active sessions that reference the old ID. Add the old ID to agentToAgent.allow until those sessions expire. 6. sessions_send timeout β€” timeoutSeconds: 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.

    βš™οΈ Configuration

  • 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