Multi-agent routing: agent boundaries, channel accounts, and bindings
Learn how to run multiple isolated agents inside a single Gateway process. Each agent gets its own workspace, session history, and channel accounts, with bindings directing incoming messages to the correct agent.
Run multiple isolated agents inside a single Gateway process. Each agent gets its own workspace, a state directory (agentDir), SQLite-backed session history, and several channel accounts (for example, two different WhatsApp numbers). Incoming messages are directed to the correct agent using bindings.
An agent represents the complete per-persona environment: workspace files, authentication profiles, a model registry, and session storage. A binding links a channel account (such as a Slack workspace or a WhatsApp number) to a specific agent.
What is one agent
Every agent has its own:
- Workspace: files,
AGENTS.md/SOUL.md/USER.md, local notes, and persona rules. - State directory (
agentDir): authentication profiles, model registry, and per-agent configuration. - Session store: chat history and routing state kept in
~/.openclaw/agents/<agentId>/agent/openclaw-agent.sqlite.
Authentication profiles are scoped per agent and read from:
~/.openclaw/agents/<agentId>/agent/auth-profiles.json
Note
sessions_historyis the safer way to retrieve cross-session context: it provides a bounded, redacted view instead of a full transcript dump. It removes thinking block signatures, tool result payload details,<relevant-memories>scaffolding, tool-call XML tags (<tool_call>,<function_call>, and their plural or downgraded forms), and MiniMax tool-call XML. It also truncates and caps the output by byte size.
Warning
Do not reuse
agentDiracross agents. Doing so causes authentication and session state conflicts. If a secondary agent's local OAuth credential expires or its refresh fails, OpenClaw falls back to the default or main agent's credential for the same profile ID and uses whichever token is newer, without copying the refresh token into the secondary agent's store. For a fully independent OAuth account, sign in from that agent directly. If you manually copy credentials, only copy portable staticapi_keyortokenprofiles. OAuth refresh material is not portable by default, thoughcopyToAgentscan explicitly opt a profile in.
Skills are loaded from each agent's workspace and from shared roots like ~/.openclaw/skills, then filtered by the effective agent skill allowlist. Use agents.defaults.skills for a shared baseline and agents.entries.*.skills for a per-agent replacement (explicit entries replace the default; they do not merge). See Skills: per-agent vs shared and Skills: agent allowlists.
Plugin-owned storage follows that plugin's configuration. Adding a second agent does not automatically split every global plugin store. For example, configure Memory Wiki per-agent vaults when personas should not share compiled wiki knowledge.
Note
Workspace note: each agent's workspace is the default current working directory, not a strict sandbox. Relative paths resolve inside the workspace, but absolute paths can reach other host locations unless sandboxing is enabled. See Sandboxing.
Paths
| What | Default | Override |
|---|---|---|
| Config | ~/.openclaw/openclaw.json | OPENCLAW_CONFIG_PATH |
| State dir | ~/.openclaw | OPENCLAW_STATE_DIR |
| Default agent's workspace | ~/.openclaw/workspace (or workspace-<profile> when OPENCLAW_PROFILE is set) | agents.entries.*.workspace, then agents.defaults.workspace, or OPENCLAW_WORKSPACE_DIR |
| Other agents' workspace | <stateDir>/workspace-<agentId> (or <agents.defaults.workspace>/<agentId> when set) | agents.entries.*.workspace |
| Agent dir | ~/.openclaw/agents/<agentId>/agent | agents.entries.*.agentDir |
| Sessions and transcripts | ~/.openclaw/agents/<agentId>/agent/openclaw-agent.sqlite | , |
| Legacy/archive session artifacts | ~/.openclaw/agents/<agentId>/sessions | , |
Single-agent mode (default)
If you do not configure anything, OpenClaw runs a single agent:
agentIddefaults tomain.- Sessions are keyed as
agent:main:<mainKey>(defaultmainKeyismain). - The workspace defaults to
~/.openclaw/workspace(orworkspace-<profile>whenOPENCLAW_PROFILEis set to something other thandefault). - The state defaults to
~/.openclaw/agents/main/agent.
Agent helper
Add a new isolated agent:
openclaw agents add work
Flags: --workspace <dir>, --model <id>, --agent-dir <dir>, --bind <channel[:accountId]> (repeatable), --non-interactive (requires --workspace).
Add bindings to direct incoming messages (the wizard can do this automatically), then confirm:
openclaw agents list --bindings
Quick start
Create each agent workspace
openclaw agents add coding
openclaw agents add social
Each agent receives its own workspace containing SOUL.md, AGENTS.md, and optionally USER.md, along with a dedicated agentDir and session store located under ~/.openclaw/agents/<agentId>.
Create channel accounts
Set up one account per agent on your chosen platforms:
- Discord: deploy one bot per agent, turn on Message Content Intent, and save each token.
- Telegram: create one bot per agent through BotFather and save each token.
- WhatsApp: associate one phone number per account.
openclaw channels login --channel whatsapp --account work
Refer to platform-specific guides: Discord, Telegram, WhatsApp.
Add agents, accounts, and bindings
Register agents under agents.entries, channel accounts under channels.<channel>.accounts, and link them together with bindings (see examples below).
Restart and verify
openclaw gateway restart
openclaw agents list --bindings
openclaw channels status --probe
Multiple agents, multiple personas
Every configured agentId defines a separate persona boundary for core agent state:
- Distinct accounts per channel (based on
accountId). - Unique personalities (via per-agent
AGENTS.md/SOUL.md). - Isolated authentication and sessions, with cross-agent access permitted only through explicit features or plugin configuration.
This setup allows multiple users to share a single Gateway while keeping each agent's core state isolated.
Per-agent Memory Wiki vaults
Memory Wiki uses one global vault by default. To separate a support agent's
compiled knowledge from a marketing agent's, assign
plugins.entries.memory-wiki.config.vault.scope the value agent:
{
plugins: {
entries: {
"memory-wiki": {
enabled: true,
config: {
vault: {
scope: "agent",
path: "~/.openclaw/wiki",
},
},
},
},
},
}
The specified path acts as the parent directory. OpenClaw appends the normalized
agent id, resulting in paths like ~/.openclaw/wiki/support and
~/.openclaw/wiki/marketing. When multiple agents are configured, agent-scoped CLI and Gateway operations
require an explicit agent identifier. Refer to
Memory Wiki per-agent vaults for details on bridge
filtering, migration, and trust boundaries.
Cross-agent QMD memory search
To allow one agent to search another agent's QMD session transcripts, add extra collections under agents.entries.*.memory.search.qmd.extraCollections. Use memory.search.qmd.extraCollections when all agents should access the same collections.
{
agents: {
defaults: {
workspace: "~/workspaces/main",
},
entries: {
main: {
workspace: "~/workspaces/main",
memory: {
search: {
qmd: {
extraCollections: [{ path: "notes" }], // resolves inside workspace -> collection named "notes-main"
},
},
},
},
family: { workspace: "~/workspaces/family" },
},
},
memory: {
backend: "qmd",
search: {
qmd: {
extraCollections: [{ path: "~/agents/family/sessions", name: "family-sessions" }],
},
},
qmd: { includeDefaultMemory: false },
},
}
An extra-collection path can be shared among agents, but its name remains explicit when the path falls outside the agent workspace. Paths inside the workspace stay agent-scoped so each agent maintains its own transcript search set.
One WhatsApp number, multiple people (DM split)
Direct different WhatsApp DMs to different agents on a single WhatsApp account by matching the sender's E.164 number (+15551234567) with peer.kind: "direct". Replies still originate from the same WhatsApp number, there is no per-agent sender identity.
Note
Direct conversations collapse to the agent's primary session key by default, so true isolation requires one agent per person.
{
agents: {
list: [
{ id: "alex", workspace: "~/.openclaw/workspace-alex" },
{ id: "mia", workspace: "~/.openclaw/workspace-mia" },
],
},
bindings: [
{
agentId: "alex",
match: { channel: "whatsapp", peer: { kind: "direct", id: "+15551230001" } },
},
{
agentId: "mia",
match: { channel: "whatsapp", peer: { kind: "direct", id: "+15551230002" } },
},
],
channels: {
whatsapp: {
dmPolicy: "allowlist",
allowFrom: ["+15551230001", "+15551230002"],
},
},
}
DM access control (pairing and allowlist) applies globally per WhatsApp account, not per agent. For shared groups, assign the group to one agent or use Broadcast groups.
Routing rules
Bindings follow deterministic rules where the most specific match wins. Consult Channel routing for the complete tier hierarchy (exact peer, parent peer, peer wildcard, guild+roles, guild, team, account, channel, default agent). Several notable rules:
- When multiple bindings match within the same tier, the first one in configuration order takes precedence.
- If a binding specifies multiple match fields (for instance
peer+guildId), every listed field must match (ANDsemantics). - A binding that omits
accountIdmatches only the default account, not all accounts. UseaccountId: "*"for a channel-wide fallback, oraccountId: "<name>"for a single account. Adding the same binding again with an explicit account id upgrades the existing channel-only binding rather than duplicating it.
Multiple accounts / phone numbers
Channels that support multiple accounts (such as WhatsApp) use accountId to distinguish each login. Each accountId routes to its own agent, so a single server can host multiple phone numbers without mixing sessions.
Set channels.<channel>.defaultAccount to specify the account used when accountId is not provided. When unset, OpenClaw falls back to default if available, otherwise the first configured account id (sorted).
Channels that support multiple accounts: discord, feishu, googlechat, imessage, irc, line, mattermost, matrix, nextcloud-talk, nostr, signal, slack, telegram, whatsapp, zalo, zalouser.
Concepts
agentId: a single "brain" (workspace, per-agent authentication, per-agent session store).accountId: one channel account instance (for example, a WhatsApp accountpersonalcompared tobiz).binding: directs incoming messages to anagentIdbased on(channel, accountId, peer), and optionally guild or team IDs.- Direct conversations reduce to
agent:<agentId>:<mainKey>(per-agent "main"; refer tosession.mainKey).
Platform examples
Discord bots per agent
Each Discord bot account is tied to a unique accountId. Link every account to an agent and maintain allowlists for each bot.
{
agents: {
list: [
{ id: "main", workspace: "~/.openclaw/workspace-main" },
{ id: "coding", workspace: "~/.openclaw/workspace-coding" },
],
},
bindings: [
{ agentId: "main", match: { channel: "discord", accountId: "default" } },
{ agentId: "coding", match: { channel: "discord", accountId: "coding" } },
],
channels: {
discord: {
groupPolicy: "allowlist",
accounts: {
default: {
token: "DISCORD_BOT_TOKEN_MAIN",
guilds: {
"123456789012345678": {
channels: {
"222222222222222222": { allow: true, requireMention: false },
},
},
},
},
coding: {
token: "DISCORD_BOT_TOKEN_CODING",
guilds: {
"123456789012345678": {
channels: {
"333333333333333333": { allow: true, requireMention: false },
},
},
},
},
},
},
},
}
- Add each bot to the guild and activate Message Content Intent.
- Tokens are stored in
channels.discord.accounts.<id>.token(the default account may useDISCORD_BOT_TOKEN).
Telegram bots per agent
{
agents: {
list: [
{ id: "main", workspace: "~/.openclaw/workspace-main" },
{ id: "alerts", workspace: "~/.openclaw/workspace-alerts" },
],
},
bindings: [
{ agentId: "main", match: { channel: "telegram", accountId: "default" } },
{ agentId: "alerts", match: { channel: "telegram", accountId: "alerts" } },
],
channels: {
telegram: {
accounts: {
default: {
botToken: "123456:ABC...",
dmPolicy: "pairing",
},
alerts: {
botToken: "987654:XYZ...",
dmPolicy: "allowlist",
allowFrom: ["tg:123456789"],
},
},
},
},
}
- Use BotFather to create one bot per agent and copy each token.
- Tokens reside in
channels.telegram.accounts.<id>.botToken(the default account can rely onTELEGRAM_BOT_TOKEN). - When running multiple bots in the same Telegram group, invite each bot and mention the one that should reply.
- Turn off BotFather Privacy Mode for each group bot (
/setprivacy-> Disable), then remove and re-add the bot so Telegram applies the change. - Authorize groups with
channels.telegram.groups, or restrictgroupPolicy: "open"to trusted group setups only. - Place sender user IDs in
groupAllowFrom. Group and supergroup IDs go intochannels.telegram.groups, notgroupAllowFrom. - Bind using
accountIdso every bot directs traffic to its own agent.
WhatsApp numbers per agent
Before starting the gateway, link each account:
openclaw channels login --channel whatsapp --account personal
openclaw channels login --channel whatsapp --account biz
~/.openclaw/openclaw.json (JSON5):
{
agents: {
list: [
{
id: "home",
default: true,
name: "Home",
workspace: "~/.openclaw/workspace-home",
agentDir: "~/.openclaw/agents/home/agent",
},
{
id: "work",
name: "Work",
workspace: "~/.openclaw/workspace-work",
agentDir: "~/.openclaw/agents/work/agent",
},
],
},
// Deterministic routing: first match wins (most-specific first).
bindings: [
{ agentId: "home", match: { channel: "whatsapp", accountId: "personal" } },
{ agentId: "work", match: { channel: "whatsapp", accountId: "biz" } },
// Optional per-peer override (example: send a specific group to work agent).
{
agentId: "work",
match: {
channel: "whatsapp",
accountId: "personal",
peer: { kind: "group", id: "1203630...@g.us" },
},
},
],
// Off by default: agent-to-agent messaging must be explicitly enabled + allowlisted.
tools: {
agentToAgent: {
enabled: false,
allow: ["home", "work"],
},
},
channels: {
whatsapp: {
accounts: {
personal: {
// Optional override. Default: ~/.openclaw/credentials/whatsapp/personal
// authDir: "~/.openclaw/credentials/whatsapp/personal",
},
biz: {
// Optional override. Default: ~/.openclaw/credentials/whatsapp/biz
// authDir: "~/.openclaw/credentials/whatsapp/biz",
},
},
},
},
}
Common patterns
WhatsApp daily + Telegram deep work
Separate by channel: route WhatsApp to a fast everyday agent and Telegram to an Opus agent.
{
agents: {
list: [
{
id: "chat",
name: "Everyday",
workspace: "~/.openclaw/workspace-chat",
model: "anthropic/claude-sonnet-4-6",
},
{
id: "opus",
name: "Deep Work",
workspace: "~/.openclaw/workspace-opus",
model: "anthropic/claude-opus-4-6",
},
],
},
bindings: [
{ agentId: "chat", match: { channel: "whatsapp", accountId: "*" } },
{ agentId: "opus", match: { channel: "telegram", accountId: "*" } },
],
}
These examples use accountId: "*" so bindings stay valid if you add accounts later. To send a single DM or group to Opus while leaving the rest on chat, add a match.peer binding for that peer. Peer matches always take priority over channel-wide rules.
Same channel, one peer to Opus
Keep WhatsApp on the fast agent, but direct one DM to Opus:
{
agents: {
list: [
{
id: "chat",
name: "Everyday",
workspace: "~/.openclaw/workspace-chat",
model: "anthropic/claude-sonnet-4-6",
},
{
id: "opus",
name: "Deep Work",
workspace: "~/.openclaw/workspace-opus",
model: "anthropic/claude-opus-4-6",
},
],
},
bindings: [
{
agentId: "opus",
match: { channel: "whatsapp", accountId: "*", peer: { kind: "direct", id: "+15551234567" } },
},
{ agentId: "chat", match: { channel: "whatsapp", accountId: "*" } },
],
}
Peer bindings always win, so place them above the channel-wide rule.
Family agent bound to a WhatsApp group
Attach a dedicated family agent to a single WhatsApp group, with mention gating and a stricter tool policy:
{
agents: {
list: [
{
id: "family",
name: "Family",
workspace: "~/.openclaw/workspace-family",
identity: { name: "Family Bot" },
groupChat: {
mentionPatterns: ["@family", "@familybot", "@Family Bot"],
},
sandbox: {
mode: "all",
scope: "agent",
},
tools: {
allow: [
"exec",
"read",
"sessions_list",
"sessions_history",
"sessions_send",
"sessions_spawn",
"session_status",
],
deny: ["write", "edit", "apply_patch", "browser", "canvas", "nodes", "cron"],
},
},
],
},
bindings: [
{
agentId: "family",
match: {
channel: "whatsapp",
peer: { kind: "group", id: "120363999999999999@g.us" },
},
},
],
}
Tool allow and deny lists refer to tools, not skills. If a skill must run a binary, make sure exec is permitted and the binary exists inside the sandbox. For tighter control, set agents.entries.*.groupChat.mentionPatterns and keep group allowlists enabled for the channel.
Per-agent sandbox and tool configuration
Every agent can have its own sandbox and tool restrictions:
{
agents: {
list: [
{
id: "personal",
workspace: "~/.openclaw/workspace-personal",
sandbox: {
mode: "off", // No sandbox for personal agent
},
// No tool restrictions - all tools available
},
{
id: "family",
workspace: "~/.openclaw/workspace-family",
sandbox: {
mode: "all", // Always sandboxed
scope: "agent", // One container per agent
docker: {
// Optional one-time setup after container creation
setupCommand: "apt-get update && apt-get install -y git curl",
},
},
tools: {
allow: ["read"], // Only read tool
deny: ["exec", "write", "edit", "apply_patch"], // Deny others
},
},
],
},
}
Note
setupCommandis located insidesandbox.dockerand executes a single time during container creation. When the resolved scope equals"shared", per-agentsandbox.docker.*overrides are disregarded.
This provides the following advantages:
- Security isolation: limit which tools an untrusted agent can access.
- Resource control: place certain agents in a sandbox while leaving others on the host.
- Flexible policies: assign distinct permissions to each agent.
Note
tools.elevatedincludes both a global gate (tools.elevated.enabled/allowFrom) and a per-agent gate (agents.entries.*.tools.elevated.enabled/allowFrom). The per-agent gate can only tighten the global gate's restrictions. Both must permit a sender before elevated commands can execute. For group targeting, useagents.entries.*.groupChat.mentionPatternsso @mentions correctly resolve to the target agent.
Detailed examples are available in Multi-agent sandbox and tools.
Related
- ACP agents, running external coding harnesses
- Channel routing, how messages route to agents
- Presence, agent presence and availability
- Session, session isolation and routing
- Sub-agents, spawning background agent runs