Agent coordination

Multi-agent sandbox and tools

Status: active

Each agent in a multi-agent setup can override the global sandbox and tool policy. This page covers per-agent configuration, precedence rules, and examples.


Configuration examples

Example 1: Personal + restricted family agent
json
{  "agents": {    "entries": {      "main": {        "default": true,        "name": "Personal Assistant",        "workspace": "~/.openclaw/workspace",        "sandbox": { "mode": "off" }      },      "family": {        "name": "Family Bot",        "workspace": "~/.openclaw/workspace-family",        "sandbox": {          "mode": "all",          "scope": "agent"        },        "tools": {          "allow": ["read", "message"],          "deny": ["exec", "write", "edit", "apply_patch", "process", "browser"],          "message": {            "crossContext": {              "allowWithinProvider": false,              "allowAcrossProviders": false            }          }        }      }    }  },  "bindings": [    {      "agentId": "family",      "match": {        "channel": "whatsapp",        "accountId": "*",        "peer": {          "kind": "group",          "id": "[email protected]"        }      }    }  ]}

Result:

  • main agent: runs on host, full tool access.
  • family agent: runs in the configured container sandbox backend (one container per agent), only read and current-conversation message sends.
Example 2: Work agent with shared sandbox
json
{  "agents": {    "entries": {      "personal": {        "default": true,        "workspace": "~/.openclaw/workspace-personal",        "sandbox": { "mode": "off" }      },      "work": {        "workspace": "~/.openclaw/workspace-work",        "sandbox": {          "mode": "all",          "scope": "shared",          "workspaceRoot": "/tmp/work-sandboxes"        },        "tools": {          "allow": ["read", "write", "apply_patch", "exec"],          "deny": ["browser", "gateway", "discord"]        }      }    }  }}
Example 2b: Global coding profile + messaging-only agent
json
{  "tools": { "profile": "coding" },  "agents": {    "entries": {      "main": {        "default": true      },      "support": {        "tools": { "profile": "messaging", "allow": ["slack"] }      }    }  }}

Result:

  • default agents get coding tools.
  • support agent is messaging-only (+ Slack tool).
Example 3: Different sandbox modes per agent
json
{  "agents": {    "defaults": {      "sandbox": {        "mode": "non-main",        "scope": "session"      }    },    "entries": {      "main": {        "default": true,        "workspace": "~/.openclaw/workspace",        "sandbox": {          "mode": "off"        }      },      "public": {        "workspace": "~/.openclaw/workspace-public",        "sandbox": {          "mode": "all",          "scope": "agent"        },        "tools": {          "allow": ["read"],          "deny": ["exec", "write", "edit", "apply_patch"]        }      }    }  }}

Configuration precedence

When both global (agents.defaults.*) and agent-specific (agents.entries.*.*) configs exist:

Sandbox config

Agent-specific settings override global:

text
agents.entries.*.sandbox.mode > agents.defaults.sandbox.modeagents.entries.*.sandbox.scope > agents.defaults.sandbox.scopeagents.entries.*.sandbox.workspaceRoot > agents.defaults.sandbox.workspaceRootagents.entries.*.sandbox.workspaceAccess > agents.defaults.sandbox.workspaceAccessagents.entries.*.sandbox.docker.* > agents.defaults.sandbox.docker.*agents.entries.*.sandbox.browser.* > agents.defaults.sandbox.browser.*agents.entries.*.sandbox.prune.* > agents.defaults.sandbox.prune.*

Tool restrictions

The filtering order is:

  • Tool profile

    tools.profile or agents.entries.*.tools.profile.

  • Provider tool profile

    tools.byProvider[provider].profile or agents.entries.*.tools.byProvider[provider].profile.

  • Global tool policy

    tools.allow / tools.deny.

  • Provider tool policy

    tools.byProvider[provider].allow/deny.

  • Agent-specific tool policy

    agents.entries.*.tools.allow/deny.

  • Agent provider policy

    agents.entries.*.tools.byProvider[provider].allow/deny.

  • Sandbox tool policy

    tools.sandbox.tools or agents.entries.*.tools.sandbox.tools.

  • Subagent tool policy

    tools.subagents.tools, if applicable.

  • Precedence rules
    • Each level can further restrict tools, but cannot grant back denied tools from earlier levels.
    • If agents.entries.*.tools.sandbox.tools is set, it replaces tools.sandbox.tools for that agent.
    • If agents.entries.*.tools.profile is set, it overrides tools.profile for that agent.
    • Provider tool keys accept either provider (e.g. anthropic) or provider/model (e.g. openai/gpt-5.4).
    Empty allowlist behavior

    If any explicit allowlist in that chain leaves the run with no callable tools, OpenClaw stops before submitting the prompt to the model. This is intentional: an agent configured with a missing tool such as agents.entries.*.tools.allow: ["query_db"] should fail loudly until the plugin that registers query_db is enabled, not continue as a text-only agent.

    Tool policies support group:* shorthands that expand to multiple tools. See Tool groups for the full list.

    Configured MCP tools use the same policy surface. Their canonical names are <safe-server>__<safe-tool>; globs can target a server namespace. For example:

    json5
    {  agents: {    entries: {      research: {        tools: {          allow: ["docs__read_docs"],          deny: ["docs__delete_*"],        },      },    },  },}

    Every restrictive layer intersects with the earlier layers, and deny always wins. OpenClaw projects the resulting raw tool set into native Claude, Codex, and Gemini MCP filters before their first model turn. Backend-native names and settings are implementation details, not a second operator policy surface. An MCP server with no allowed tool is omitted. A restrictive catalog failure also omits that server and records a diagnostic instead of failing open.

    Per-agent elevated overrides (agents.entries.*.tools.elevated) can further restrict elevated exec for specific agents. See Elevated mode for details.


    Migration from single agent

    Before (single agent)

    json
    {  "agents": {    "defaults": {      "workspace": "~/.openclaw/workspace",      "sandbox": {        "mode": "non-main"      }    }  },  "tools": {    "sandbox": {      "tools": {        "allow": ["read", "write", "apply_patch", "exec"],        "deny": []      }    }  }}

    After (multi-agent)

    json
    {  "agents": {    "entries": {      "main": {        "default": true,        "workspace": "~/.openclaw/workspace",        "sandbox": { "mode": "off" }      }    }  }}

    Tool restriction examples

    Read-only agent

    json
    {  "tools": {    "allow": ["read"],    "deny": ["exec", "write", "edit", "apply_patch", "process"]  }}

    Shell execution with filesystem tools disabled

    json
    {  "tools": {    "allow": ["read", "exec", "process"],    "deny": ["write", "edit", "apply_patch", "browser", "gateway"]  }}

    Communication-only

    This complete configuration applies the tool allow/deny policy to the communication agent and sets session visibility for every agent on the Gateway:

    json
    {  "tools": {    "sessions": { "visibility": "tree" }  },  "agents": {    "entries": {      "communication": {        "tools": {          "allow": ["sessions_list", "sessions_send", "sessions_history", "session_status"],          "deny": ["exec", "write", "edit", "apply_patch", "read", "browser"]        }      }    }  }}

    tools.sessions.visibility is Gateway-wide and cannot be set per agent. Session tools default to all with agent-to-agent messaging on. With tree, callers can access their current session and sessions they spawn; the canonical main session can still access every session belonging to its agent. Incognito restrictions and the sandbox spawned-session clamp still apply. See tools.sessions and tools.agentToAgent.

    sessions_history in this profile still returns a bounded, sanitized recall view rather than a raw transcript dump. Assistant recall strips thinking tags, <relevant-memories> scaffolding, plain-text tool-call XML payloads (including <tool_call>...</tool_call>, <function_call>...</function_call>, <tool_calls>...</tool_calls>, <function_calls>...</function_calls>, and truncated tool-call blocks), downgraded tool-call scaffolding, leaked ASCII/full-width model control tokens, and malformed MiniMax tool-call XML before redaction/truncation.


    Common pitfall: "non-main"


    Testing

    After configuring multi-agent sandbox and tools:

  • Check agent resolution

    bash
    openclaw agents list --bindings
  • Verify sandbox containers

    bash
    docker ps --filter "name=openclaw-sbx-"
  • Test tool restrictions

    • Send a message requiring restricted tools.
    • Verify the agent cannot use denied tools.
  • Monitor logs

    bash
    openclaw logs --follow | grep -E "routing|sandbox|tools"

  • Troubleshooting

    Agent not sandboxed despite `mode: 'all'`
    • Check if there's a global agents.defaults.sandbox.mode that overrides it.
    • Agent-specific config takes precedence, so set agents.entries.*.sandbox.mode: "all".
    Tools still available despite deny list
    • Check the full filtering order: profile → provider profile → global policy → provider policy → agent policy → agent provider policy → sandbox → subagent.
    • Each level can only further restrict, not grant back.
    • See Sandbox vs tool policy vs elevated for step-by-step debugging.
    • For MCP tools, use the provider-safe name shown by OpenClaw, such as docs__read_docs or docs__*; do not use a backend's raw config field name.
    Container not isolated per agent
    • Default scope is "agent" (one container per agent id).
    • Set scope: "session" for one container per session, or scope: "shared" to reuse one container across agents.

    Was this useful?
    On this page

    On this page