Recommended Configuration

Recommended Configuration

Complete annotated openclaw.json implementing the recommended two-agent architecture: main (sandboxed, channel-facing, full exec + browser on egress-allowlisted network) and search (web only, no filesystem). Core guard plugins enabled (channel-guard, content-guard). Uses JSON5 comments for inline documentation — OpenClaw supports JSON5 natively. For maximum hardening with deterministic guards (file-guard, network-guard, command-guard), see Hardened Multi-Agent .

Main runs on openclaw-egress — a custom Docker network with host-level firewall rules restricting outbound to pre-approved hosts (npm, git, Playwright CDN, etc.). See scripts/network-egress/ for setup. For exec-separated architecture with a dedicated computer agent, see Hardened Multi-Agent . For a minimal starting point (single channel, two agents, no egress), see Basic Configuration .

Three deployment postures are covered: Docker isolation (this config), macOS VM isolation (remove sandbox blocks), and Linux VM isolation (keep sandbox blocks). See Phase 3 — Security for the full trade-off analysis.

{
  // ============================================================
  // OpenClaw Configuration — Recommended Multi-Agent Setup
  // ============================================================
  // NOTE: This file uses JSON5 comments (//) for documentation.
  // OpenClaw supports JSON5 natively — no need to strip comments.
  //
  // Two-agent architecture:
  //   Main agent   — channel-facing, sandboxed (Docker, egress-allowlisted network),
  //                  full exec + browser. Delegates web search to search agent.
  //   Search agent — web_search/web_fetch only, no filesystem, no exec.
  //
  // For exec-separated architecture with a dedicated computer agent,
  // see hardened-multi-agent.md.
  //
  // Optional: Dedicated channel agents (defense-in-depth) — commented out below.
  //   Uncomment to route channels to restricted agents instead of main.
  //
  // Prerequisites:
  //   1. Guard plugins installed (channel-guard, content-guard)
  //      OPENROUTER_API_KEY required for content-guard
  //   2. Docker running
  //
  // OPTIONAL HARDENING PLUGINS (not included here — see hardened-multi-agent.md):
  //   file-guard, network-guard, command-guard — deterministic guards for
  //   path protection, domain allowlisting, and dangerous command blocking.
  //   3. Egress-allowlisted network created (see scripts/network-egress/)
  //
  // DEPLOYMENT OPTIONS:
  //   Docker isolation — Dedicated OS user + Docker (this config): stronger
  //                      internal agent isolation via Docker sandboxing.
  //   VM: macOS VMs — Lume / Parallels: stronger host isolation, no Docker inside VM.
  //                   Remove all "sandbox" blocks; tool policy provides internal isolation.
  //   VM: Linux VMs — Multipass / KVM: strongest combined posture (VM boundary + Docker).
  //                   Keep "sandbox" blocks — Docker works inside Linux VMs.
  //   See Phase 3 — Security for the full trade-off analysis.
  //
  // Replace placeholder values:
  //   +46XXXXXXXXX  → your phone number
  //   user@yourdomain.com → your Google Chat email
  //   <node-name>.<tailnet> → your Tailscale node
  //
  // Environment variables (set in LaunchAgent plist or systemd EnvironmentFile, or LaunchDaemon for hardened setup):
  //   OPENCLAW_GATEWAY_TOKEN
  //   ANTHROPIC_API_KEY
  //   BRAVE_API_KEY (or OPENROUTER_API_KEY for Perplexity)
  //   OPENROUTER_API_KEY (required for content-guard and image-gen; also used by Perplexity)
  //   GITHUB_TOKEN
  //   GOOGLE_CHAT_SERVICE_ACCOUNT_FILE (path to service account JSON key)
  // See Phase 6 — Deployment > Secrets Management for details.
  // ============================================================

  // --- Chat Commands ---
  // Disable dangerous chat commands. These allow users to
  // run shell commands, edit config, or restart the gateway
  // from within a chat message.
  "commands": {
    "native": "auto",
    "nativeSkills": "auto",
    "bash": false,       // Blocks !command shell access
    "config": false,     // Blocks /config writes
    "debug": false,      // Blocks /debug runtime overrides
    "restart": false     // Blocks /restart
  },

  // --- Global Tool Restrictions ---
  "tools": {
    // Only deny tools globally that NO agent should ever have.
    // Per-agent tools (exec, browser, web_search, etc.) are controlled in each
    // agent's allow/deny — global deny overrides agent-level allow.
    "deny": ["canvas", "gateway", "nodes"],

    // Disable elevated mode globally — prevents sandbox escape
    "elevated": { "enabled": false },

    // Sandbox tool policy — the default allow list excludes message, browser, and memory tools.
    // WhatsApp DMs and all non-main sessions are sandboxed even with mode:"non-main".
    // alsoAllow does NOT work with the default policy — must provide the full allow list.
    // Verify: openclaw sandbox explain --agent <id> --session "<session-key>"
    "sandbox": {
      "tools": {
        "allow": [
          "exec", "process", "read", "write", "edit", "apply_patch", "image",
          "sessions_list", "sessions_history", "sessions_send", "sessions_spawn",
          "subagents", "session_status",
          "message", "browser",
          "memory_search", "memory_get"
        ]
      }
    },

    // Web search provider config (accessible only to agents that allow web_search)
    "web": {
      "search": {
        "enabled": true,
        "provider": "brave",
        "apiKey": "${BRAVE_API_KEY}"

        // Alternative: Perplexity via OpenRouter
        // "provider": "perplexity",
        // "perplexity": {
        //   "apiKey": "${OPENROUTER_API_KEY}",
        //   "baseUrl": "https://openrouter.ai/api/v1",
        //   "model": "perplexity/sonar-pro"
        // }
      }
    }
  },

  // --- Skills ---
  // Only allow known-good bundled skills. No skill installer configured
  // means no new skills can be added from ClawHub.
  "skills": {
    "allowBundled": ["coding-agent", "github", "healthcheck", "weather", "video-frames"]
  },

  // --- Session Isolation ---
  "session": {
    // Each sender on each channel gets their own isolated session
    "dmScope": "per-channel-peer",

    // Inter-agent communication settings
    "agentToAgent": {
      "maxPingPongTurns": 5
    }
  },

  // --- Agents ---
  "agents": {
    "defaults": {
      // Pre-compaction memory flush — saves important context to memory before compacting.
      // Without this, the agent forgets everything from the compacted conversation portion.
      "compaction": {
        "mode": "safeguard",
        "reserveTokensFloor": 20000,
        "memoryFlush": {
          "enabled": true,
          "softThresholdTokens": 4000,
          "systemPrompt": "Session nearing compaction. Store durable memories now.",
          "prompt": "Write any lasting notes to memory/YYYY-MM-DD.md; reply with NO_REPLY if nothing to store."
        }
      },

      // Memory search — semantic + keyword hybrid search across memory files.
      // Local provider: no API key, ~600MB disk, full privacy.
      "memorySearch": {
        "enabled": true,
        "provider": "local",
        // "fallback": "none",          // Don't fall back to remote on local failure
        "query": {
          "hybrid": {
            "enabled": true,
            "vectorWeight": 0.7,
            "textWeight": 0.3
            // "mmr": { "enabled": true, "lambda": 0.7 }  // Deduplicate similar results
          }
        },
        // "temporalDecay": { "enabled": true, "halfLifeDays": 30 },  // Boost recent results
        "cache": {
          "enabled": true,
          "maxEntries": 50000
        }
      },
      "maxConcurrent": 4,
      "subagents": {
        "maxConcurrent": 8,
        "model": "anthropic/claude-sonnet-4-5",
        "thinking": "low",
        // "maxSpawnDepth": 2,          // Max nesting depth for nested sub-agents; default changed from 3→2 in 2026.2.21
        // "maxChildrenPerAgent": 10    // Max concurrent children per parent agent (2026.2.16+)
      }
      // NOTE: No default sandbox block — each agent defines its own sandbox explicitly.
      // Both core agents use mode:"all" with different network settings,
      // so a shared default would be misleading.
    },
    "list": [
      {
        // MAIN AGENT — channel-facing, full exec + browser on egress-allowlisted network
        // mode:"non-main" — Control UI/HTTP API session runs on host (operator trusted);
        // all channel sessions (WhatsApp DMs, groups, cron) sandboxed in Docker on openclaw-egress.
        // web_search and web_fetch denied — all web content goes through search agent + content-guard.
        // IMPORTANT: Create network FIRST: docker network create openclaw-egress
        // Gateway startup will fail if network doesn't exist.
        // See scripts/network-egress/ for egress allowlisting setup.
        "id": "main",
        "default": true,
        "workspace": "/Users/openclaw/.openclaw/workspaces/main",
        "agentDir": "/Users/openclaw/.openclaw/agents/main/agent",
        "tools": {
          "allow": ["group:runtime", "group:fs", "group:sessions", "memory_search", "memory_get", "message", "browser"],
          "deny": ["web_search", "web_fetch", "canvas", "group:automation"],
          "elevated": { "enabled": false }
        },
        "subagents": { "allowAgents": ["search"] },
        // Signal has no native @mention — regex patterns for group mention gating.
        "groupChat": {
          "mentionPatterns": ["@openclaw", "hey openclaw"]
        },
        "sandbox": {
          "mode": "non-main",
          "scope": "agent",
          "workspaceAccess": "rw",
          "docker": { "network": "openclaw-egress" }
        }
      },
      {
        // SEARCH AGENT — isolated web search + content processing
        // Only reachable via sessions_send from main.
        // No channel binding = can't be messaged directly.
        // No filesystem tools — tool policy provides isolation.
        // Unsandboxed — workaround for #9857 (sessions_spawn broken when both agents sandboxed + per-agent tools).
        // Cheaper model — search queries don't need Opus.
        "id": "search",
        "workspace": "/Users/openclaw/.openclaw/workspaces/search",
        "agentDir": "/Users/openclaw/.openclaw/agents/search/agent",
        "model": "anthropic/claude-sonnet-4-5",
        "tools": {
          "allow": ["web_search", "web_fetch", "sessions_send", "session_status"],
          "deny": ["exec", "read", "write", "edit", "apply_patch", "process", "browser", "gateway", "cron"]
        },
        "subagents": { "allowAgents": [] }
      }

      // --- OPTIONAL: Dedicated channel agents (defense-in-depth) ---
      // Uncomment to route channels to restricted agents instead of main.
      // Each channel agent has no exec/process — delegates to main/search.
      // Also uncomment the corresponding bindings in the bindings section below.
      // NOTE: Requires fix for openclaw#15176 — channel bindings to non-default
      // agents are broken in 2026.2.12 (session path regression).
      //
      // ,{
      //   "id": "whatsapp",
      //   "workspace": "/Users/openclaw/.openclaw/workspaces/whatsapp",
      //   "agentDir": "/Users/openclaw/.openclaw/agents/whatsapp/agent",
      //   "tools": {
      //     "deny": ["web_search", "web_fetch", "browser", "exec", "process"],
      //     "elevated": { "enabled": false }
      //   },
      //   "subagents": { "allowAgents": ["main", "search"] }
      // },
      // {
      //   "id": "signal",
      //   "workspace": "/Users/openclaw/.openclaw/workspaces/signal",
      //   "agentDir": "/Users/openclaw/.openclaw/agents/signal/agent",
      //   "tools": {
      //     "deny": ["web_search", "web_fetch", "browser", "exec", "process"],
      //     "elevated": { "enabled": false }
      //   },
      //   "subagents": { "allowAgents": ["main", "search"] }
      // },
      // {
      //   "id": "googlechat",
      //   "workspace": "/Users/openclaw/.openclaw/workspaces/googlechat",
      //   "agentDir": "/Users/openclaw/.openclaw/agents/googlechat/agent",
      //   "tools": {
      //     "deny": ["web_search", "web_fetch", "browser", "exec", "process"],
      //     "elevated": { "enabled": false }
      //   },
      //   "subagents": { "allowAgents": ["main", "search"] }
      // }
    ]
  },

  // --- Channel Routing ---
  // All channels route to main (the default agent). Search has no binding —
  // unreachable from channels, only via sessions_send.
  // channel-guard scans inbound messages.
  //
  // OPTIONAL: Dedicated channel agents (defense-in-depth)
  // If you uncommented the channel agents above, add bindings:
  //   { "agentId": "whatsapp", "match": { "channel": "whatsapp" } },
  //   { "agentId": "signal", "match": { "channel": "signal" } },
  //   { "agentId": "googlechat", "match": { "channel": "googlechat" } }
  // NOTE: Requires fix for openclaw#15176 — channel bindings to non-default
  // agents are broken in 2026.2.12 (session path regression).

  // --- Channel Configuration ---
  "channels": {
    "whatsapp": {
      "dmPolicy": "pairing",
      "selfChatMode": false,
      "allowFrom": ["+46XXXXXXXXX"], // REPLACE with real number — placeholder causes silent message drops
      "groupPolicy": "allowlist",
      "groups": { "*": { "requireMention": true } }, // Bug #11758: broken on WhatsApp (LID transition) — use false + mentionPatterns as workaround
      "mediaMaxMb": 50,
      "debounceMs": 0
    },
    "signal": {
      "enabled": true,
      "account": "+46XXXXXXXXX",
      "dmPolicy": "pairing",
      "allowFrom": ["+46XXXXXXXXX"], // REPLACE with real number — placeholder causes silent message drops
      "groupPolicy": "allowlist",
      "groups": { "*": { "requireMention": true } },
      "mediaMaxMb": 8
    },
    "googlechat": {
      "enabled": true,
      "serviceAccountFile": "${GOOGLE_CHAT_SERVICE_ACCOUNT_FILE}",
      "audienceType": "app-url",
      "audience": "https://<node-name>.<tailnet>.ts.net/googlechat",
      "dm": {
        "policy": "allowlist",
        "allowFrom": ["user@yourdomain.com"] // REPLACE with real email — placeholder causes silent message drops
      },
      "groupPolicy": "allowlist",
      "groups": { "*": { "requireMention": true } },
      "mediaMaxMb": 20
    }
  },

  // --- Browser ---
  // Required for the main agent's browser tool. Uses a managed profile —
  // never your personal Chrome. evaluateEnabled:false blocks raw JS evaluation.
  "browser": {
    "enabled": true,
    "defaultProfile": "openclaw",
    "headless": true,
    "evaluateEnabled": false,
    "profiles": {
      "openclaw": { "cdpPort": 18800, "color": "#FF4500" }
    }
  },

  // --- Gateway ---
  "gateway": {
    "port": 18789,
    "mode": "local",
    "bind": "loopback",
    "auth": {
      "mode": "token",
      "token": "${OPENCLAW_GATEWAY_TOKEN}"
    }
  },

  // --- Network Discovery ---
  "discovery": {
    "mdns": { "mode": "minimal" }
  },

  // --- Logging ---
  "logging": {
    "redactSensitive": "tools",
    "redactPatterns": ["pplx-[A-Za-z0-9]+"]
  },

  // --- Plugins ---
  "plugins": {
    // Only allow these plugins to load. Without this, any plugin dropped into
    // ~/.openclaw/extensions/ loads automatically at startup.
    "allow": [
      "whatsapp", "signal", "googlechat",
      "channel-guard", "content-guard", "image-gen"
      // Add when using hardening plugins:
      // "file-guard", "network-guard", "command-guard"
    ],
    "entries": {
      "whatsapp": { "enabled": true },
      "signal": { "enabled": true },
      "googlechat": { "enabled": true },
      "channel-guard": {
        // LLM-based scanning of inbound channel messages before they reach agents.
        // Requires OPENROUTER_API_KEY (or openRouterApiKey in plugin config).
        // failOpen:false — block message if scanner fails (safe default).
        "enabled": true,
        "config": {
          "model": "anthropic/claude-haiku-4-5",
          "maxContentLength": 10000,
          "timeoutMs": 10000,
          "failOpen": false,
          "warnThreshold": 0.4,
          "blockThreshold": 0.8
        }
      },
      "content-guard": {
        // LLM-based injection scanning at the sessions_send trust boundary.
        // Guards search→main: prevents poisoned web content from compromising main.
        // Requires OPENROUTER_API_KEY. Fails closed — no failOpen option.
        "enabled": true,
        "config": {
          "model": "anthropic/claude-haiku-4-5",
          "maxContentLength": 50000,
          "timeoutMs": 15000
        }
      },
      // OPTIONAL HARDENING PLUGINS (see hardened-multi-agent.md for full config):
      // "file-guard": { "enabled": true, "config": { ... } },
      // "network-guard": { "enabled": true, "config": { ... } },
      // "command-guard": { "enabled": true, "config": { ... } },
      "image-gen": {
        "enabled": true,
        "config": {
          "apiKey": "${OPENROUTER_API_KEY}",
          "defaultModel": "openai/gpt-5-image-mini",
          "defaultAspectRatio": "1:1",
          "defaultImageSize": "2K",
          "timeoutMs": 60000
        }
      }
    }
  }
}
Last updated on