Skip to content

Multi-Agent Architecture

Multi-Agent Architecture

Overview

Three agents running in one OpenClaw gateway, each with a distinct security profile. The main agent handles the owner’s DMs with full host access. The group agent handles all group chats with restricted tools and exec approvals. The family agent handles family member DMs with the same restrictions.

Architecture Diagram

┌──────────────────────────────────┐
│ OpenClaw Gateway │
│ (single instance) │
└──────────┬───────────────────────┘
┌────────────────────┼─────────────────────┐
│ │ │
┌────────▼──────────┐ ┌──────▼──────────┐ ┌─────────▼─────────┐
│ main-agent │ │ group-agent │ │ family-agent │
│ sandbox: off │ │ sandbox: off │ │ sandbox: off │
└────────┬──────────┘ └───────┬─────────┘ └─────────┬─────────┘
│ │ │
┌────▼───┐ ┌────▼────┐ ┌─────▼────┐
│Owner │ │ Group │ │ Family │
│DMs │ │ Chats │ │ DMs │
│Webchat │ │(iMsg) │ │ │
└────────┘ └─────────┘ └──────────┘

Session & Sandbox Model

┌──────────────┬─────────────────┬──────────────────────┬──────────────────────────────┐
│ Context │ Agent │ Execution │ Tools │
├──────────────┼─────────────────┼──────────────────────┼──────────────────────────────┤
│ Owner DMs │ main-agent │ HOST (sandbox: off) │ FULL: all skills, exec, │
│ + webchat │ │ │ file ops, MCP, browser │
├──────────────┼─────────────────┼──────────────────────┼──────────────────────────────┤
│ Group chats │ group-agent │ HOST (sandbox: off) │ MINIMAL + web_search, │
│ (iMessage) │ │ │ web_fetch, memory, message, │
│ │ │ │ tts, sessions_list/history │
│ │ │ │ exec: ALLOWLISTED ONLY │
├──────────────┼─────────────────┼──────────────────────┼──────────────────────────────┤
│ Family DMs │ family-agent │ HOST (sandbox: off) │ Same as group-agent │
│ │ │ │ (separate workspace) │
└──────────────┴─────────────────┴──────────────────────┴──────────────────────────────┘

Why Three Agents?

The original plan used two agents with sandbox.mode: "non-main" on the main agent to sandbox group chats automatically. This was replaced by a dedicated group agent because:

  1. Explicit routing — Each message type gets a specific agent with clear tool boundaries
  2. Simpler configuration — No reliance on main/non-main session distinction
  3. Better isolation — Group chats and family DMs have restricted tool sets with exec approvals as the enforcement layer

Agent: Main Agent (Owner)

  • Routes to: Owner’s DMs, webchat, WhatsApp groups
  • Sandbox: OFF (full host access)
  • Exec approvals: security: full (unrestricted)
  • Tools: All available (exec, read, MCP via mcporter, browser, etc.)
{
"id": "main-agent",
"default": true,
"workspace": "/Users/AGENT_USER/.openclaw/agents/main-agent/workspace",
"identity": { "name": "YourAgent", "emoji": "🤖" },
"sandbox": { "mode": "off" },
"groupChat": {
"mentionPatterns": ["@youragent", "youragent", "hey youragent"]
},
"tools": {
"profile": "minimal",
"alsoAllow": ["read", "exec", "web_search", "web_fetch", "memory_search",
"memory_get", "sessions_send", "session_status", "tts", "browser",
"sessions_spawn", "image", "sessions_history", "sessions_list",
"canvas", "agents_list", "cron", "message"],
"deny": ["write", "edit", "apply_patch", "nodes", "gateway", "process"]
}
}

Agent: Group Agent (Group Chats)

  • Routes to: All iMessage group chats (explicit per-group + wildcard)
  • Sandbox: OFF (host access, restricted by tool policy + exec approvals)
  • Exec approvals: security: allowlist — only specific CLI tools
  • Tools: Minimal + web_search, web_fetch, memory, tts, message
  • Sessions: sessions_send and sessions_spawn DENIED
{
"id": "group-agent",
"workspace": "/Users/AGENT_USER/.openclaw/agents/group-agent/workspace",
"identity": { "name": "YourAgent", "emoji": "🤖" },
"sandbox": { "mode": "off" },
"tools": {
"profile": "minimal",
"alsoAllow": ["web_search", "web_fetch", "memory_search", "memory_get",
"session_status", "tts", "message", "sessions_list", "sessions_history", "exec"],
"deny": ["write", "edit", "browser", "canvas", "nodes", "cron", "gateway", "process",
"sessions_send", "sessions_spawn", "apply_patch"]
}
}

Agent: Family Agent (Family DMs)

  • Routes to: Family member DMs
  • Sandbox: OFF (host access, restricted by tool policy + exec approvals)
  • Exec approvals: security: allowlist — same as group agent
  • Tools: Same as group agent (separate workspace)
{
"id": "family-agent",
"workspace": "/Users/AGENT_USER/.openclaw/agents/family-agent/workspace",
"identity": { "name": "YourAgent", "emoji": "🤖" },
"sandbox": { "mode": "off" },
"tools": {
"profile": "minimal",
"alsoAllow": ["web_search", "web_fetch", "memory_search", "memory_get",
"session_status", "tts", "message", "sessions_list", "sessions_history", "exec"],
"deny": ["write", "edit", "browser", "canvas", "nodes", "cron", "gateway", "process",
"sessions_send", "sessions_spawn", "apply_patch"]
}
}

Heartbeat: Both family and group agents should have HEARTBEAT.md kept with only comments (no tasks). This causes OpenClaw to skip the heartbeat API call entirely, saving tokens.


Channel Bindings

Bindings are deterministic and most-specific wins. Peer-level bindings take priority over channel-wide rules.

"bindings": [
// Family members -> family-agent (sandboxed)
{ "agentId": "family-agent", "match": { "channel": "bluebubbles", "peer": { "kind": "dm", "id": "+1AAAAAAAAAA" }}},
{ "agentId": "family-agent", "match": { "channel": "bluebubbles", "peer": { "kind": "dm", "id": "+1BBBBBBBBBB" }}},
{ "agentId": "family-agent", "match": { "channel": "bluebubbles", "peer": { "kind": "dm", "id": "+1CCCCCCCCCC" }}},
// Specific group chats -> group-agent (restricted)
{ "agentId": "group-agent", "match": { "channel": "bluebubbles", "peer": { "kind": "group", "id": "any;+;XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX" }}},
// Wildcard fallback for any other groups -> group-agent
{ "agentId": "group-agent", "match": { "channel": "bluebubbles", "peer": { "kind": "group", "id": "*" }}},
// Catch-all -> main-agent (owner DM, webchat, any unmatched)
{ "agentId": "main-agent", "match": { "channel": "bluebubbles" }},
{ "agentId": "main-agent", "match": { "channel": "webchat" }}
]

Important: Without explicit bindings, all messages fall through to the default agent (main-agent) which has full host access. Always add bindings for every channel/peer combination.


Exec Approvals (Per-Agent Command Allowlisting)

Exec approvals are the critical security layer that prevents restricted agents from running arbitrary commands even when exec is in their tool policy. This is configured in ~/.openclaw/exec-approvals.json.

Why Not Just Deny Exec?

Family members need access to travel tools, Apple PIM (calendar, reminders, contacts), and read-only mail. All of these use CLI tools invoked via exec. But unrestricted exec would also allow access to private email, sending messages, and running arbitrary commands.

Solution: CLI Allowlist + Workspace Isolation

Per-agent exec allowlists restrict exactly which commands each agent can run:

  1. Travel tools — via a wrapper script that only calls the specific MCP server
  2. Apple PIM — workspace wrappers that set config environment variables for per-agent access restrictions
  3. Read-only mail — specific mail CLI commands only

Explicitly NOT in the allowlist (blocked by absence):

  • Direct MCP client commands (blocks private email access)
  • Mail send/reply commands (blocks sending email)
  • Mail archive/mark-read commands (blocks mailbox modification)
~/.openclaw/exec-approvals.json
{
"version": 1,
"defaults": {
"security": "deny", // Block all exec by default
"ask": "off",
"askFallback": "deny"
},
"agents": {
"main-agent": {
"security": "full" // Main agent: unrestricted
},
"group-agent": {
"security": "allowlist",
"ask": "off",
"askFallback": "deny",
"autoAllowSkills": false,
"allowlist": [
{ "pattern": "/Users/AGENT_USER/.local/bin/travel-hub" },
{ "pattern": "/Users/AGENT_USER/.openclaw/agents/group-agent/workspace/bin/calendar-cli" },
{ "pattern": "/Users/AGENT_USER/.openclaw/agents/group-agent/workspace/bin/reminder-cli" },
{ "pattern": "/Users/AGENT_USER/.openclaw/agents/group-agent/workspace/bin/contacts-cli" },
{ "pattern": "/Users/AGENT_USER/.openclaw/agents/group-agent/workspace/bin/mail-cli" },
{ "pattern": "/Users/AGENT_USER/.local/bin/mail-inbox" },
{ "pattern": "/Users/AGENT_USER/.local/bin/mail-read" },
{ "pattern": "/Users/AGENT_USER/.local/bin/mail-list" },
{ "pattern": "/Users/AGENT_USER/.local/bin/mail-auth-check" }
]
},
"family-agent": {
// Same structure as group-agent, pointing to its own workspace
"security": "allowlist",
"allowlist": [ /* ... same pattern, different workspace path ... */ ]
}
}
}

Security Layers (Defense in Depth)

Family member sends a message
┌─────────────┐
│ Bindings │ → Routes to group-agent or family-agent (not main-agent)
└──────┬──────┘
┌─────────────┐
│ Tool Policy │ → exec allowed (needed for CLIs)
└──────┬──────┘
┌─────────────┐
│ Exec │ → Only allowlisted commands permitted
│ Approvals │
└──────┬──────┘
┌─────────────┐
│ Workspace │ → PIM wrappers set config for per-agent access control
│ Wrappers │
└──────┬──────┘
CLI responds (filtered by config)

If any layer is bypassed, the next one catches it:

  • Bindings wrong? → Exec approvals still block private email access
  • Exec approvals wrong? → Wrapper script only calls allowed MCP server
  • Wrapper bypassed? → Tool policy still blocks write/edit/browser

Elevated Access

Only the owner gets elevated access across all agents:

"tools": {
"elevated": {
"enabled": true,
"allowFrom": {
"bluebubbles": ["+1XXXXXXXXXX"] // Owner only
}
}
},
"agents": {
"defaults": {
"elevatedDefault": "on" // Applies to all agents for owner
}
}

Result: When the owner @mentions in a group chat, the group-agent (restricted) gets elevated mode, unlocking full exec access for that session.


Agent-to-Agent Escalation (Disabled)

Agent-to-agent messaging is disabled globally:

"tools": {
"agentToAgent": {
"enabled": false,
"allow": ["main-agent", "family-agent", "group-agent"]
}
}

Why disabled: During a security incident, agent-to-agent created a soft enforcement gap. When a restricted agent sent a request to the main agent, the main agent executed it with full host access — including private email search. This effectively bypassed the tool restrictions. Until OpenClaw adds scoped agent-to-agent (where the receiving agent inherits the sender’s restrictions), this remains off.

Workaround: If a family member needs something only the main agent can do, the agent tells them to ask the owner directly.


Auth Setup (API Keys)

Critical: openclaw onboard only configures auth for the default agent. Each additional agent needs its own auth-profiles.json or it will fail with:

⚠️ Agent failed before reply: No API key found for provider "anthropic".
Auth store: /Users/AGENT_USER/.openclaw/agents/<agent-id>/agent/auth-profiles.json

Create agent directories and copy auth

After running openclaw onboard for the main agent, copy its auth to each additional agent:

Terminal window
mkdir -p ~/.openclaw/agents/family-agent/agent
mkdir -p ~/.openclaw/agents/group-agent/agent
cp ~/.openclaw/agents/main-agent/agent/auth-profiles.json \
~/.openclaw/agents/family-agent/agent/auth-profiles.json
cp ~/.openclaw/agents/main-agent/agent/auth-profiles.json \
~/.openclaw/agents/group-agent/agent/auth-profiles.json

After rotating API keys

When you rotate API keys, remember to update auth for all agents, not just the main one.


Apple PIM Access (Workspace-Isolated)

Apple PIM (calendars, reminders, contacts, mail) uses native Swift CLIs. These are invoked via the exec tool, NOT as an MCP server.

Each agent has workspace-local PIM CLI wrappers that set a config environment variable to enforce per-agent access restrictions:

AgentCalendar AccessReminders Access
main-agentAll calendarsAll reminder lists
group-agentBlocklist: Work, PersonalBlocklist: Personal
family-agentBlocklist: Work, PersonalBlocklist: Personal

Shared Skills Directory

Shared skills live at ~/.openclaw/skills/ and are accessible to ALL agents:

SkillPurpose
travel-hub/Travel MCP — trips, flights, reservations
apple-pim/Apple Calendar, Reminders, Contacts CLIs

Agent-specific skills live in the agent’s workspace. Only the main agent has full mail access skills in its workspace.


Known Limitations

Session Stickiness

Existing sessions retain their original agent routing. After changing bindings, old sessions become orphaned. New messages create fresh sessions under the correct agent. No action needed — old sessions simply stop receiving messages.