agentDir), and session store, plus multiple channel accounts (e.g. two WhatsApp numbers). Inbound messages route to the right agent through bindings.
An agent is the full per-persona scope: workspace files, auth profiles, model registry, and session store. A binding maps a channel account (a Slack workspace, a WhatsApp number, etc.) to one of those agents.
What is one agent
Each agent has its own:- Workspace: files,
AGENTS.md/SOUL.md/USER.md, local notes, persona rules. - State directory (
agentDir): auth profiles, model registry, per-agent config. - Session store: chat history and routing state under
~/.openclaw/agents/<agentId>/sessions.
sessions_history is the safer cross-session recall path: it returns a bounded, redacted view, not a raw transcript dump. It strips thinking-block signatures, tool-result payload details, <relevant-memories> scaffolding, tool-call XML tags (<tool_call>, <function_call>, and their plural/downgraded forms), and MiniMax tool-call XML, then truncates and caps output by byte size.~/.openclaw/skills, then filter by the effective agent skill allowlist. Use agents.defaults.skills for a shared baseline and agents.list[].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.
Workspace note: each agent’s workspace is the default cwd, not a hard 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.list[].workspace, then agents.defaults.workspace, or OPENCLAW_WORKSPACE_DIR |
| Other agents’ workspace | <stateDir>/workspace-<agentId> (or <agents.defaults.workspace>/<agentId> when set) | agents.list[].workspace |
| Agent dir | ~/.openclaw/agents/<agentId>/agent | agents.list[].agentDir |
| Sessions | ~/.openclaw/agents/<agentId>/sessions | — |
Single-agent mode (default)
If you configure nothing, OpenClaw runs one agent:agentIddefaults tomain.- Sessions key as
agent:main:<mainKey>(defaultmainKeyismain). - Workspace defaults to
~/.openclaw/workspace(orworkspace-<profile>whenOPENCLAW_PROFILEis set to something other thandefault). - State defaults to
~/.openclaw/agents/main/agent.
Agent helper
Add a new isolated agent:--workspace <dir>, --model <id>, --agent-dir <dir>, --bind <channel[:accountId]> (repeatable), --non-interactive (requires --workspace).
Add bindings to route inbound messages (the wizard offers to do this for you), then verify:
Quick start
Create each agent workspace
SOUL.md, AGENTS.md, and optional USER.md, plus a dedicated agentDir and session store under ~/.openclaw/agents/<agentId>.Create channel accounts
Add agents, accounts, and bindings
Add agents under
agents.list, channel accounts under channels.<channel>.accounts, and connect them with bindings (examples below).Multiple agents, multiple personas
Each configuredagentId is a fully isolated persona:
- Different accounts per channel (per
accountId). - Different personalities (per-agent
AGENTS.md/SOUL.md). - Separate auth and sessions, with no cross-talk unless explicitly enabled.
Cross-agent QMD memory search
To let one agent search another agent’s QMD session transcripts, add extra collections underagents.list[].memorySearch.qmd.extraCollections. Use agents.defaults.memorySearch.qmd.extraCollections when every agent should share the same collections.
name stays explicit when the path is outside the agent workspace. Paths inside the workspace stay agent-scoped so each agent keeps its own transcript search set.
One WhatsApp number, multiple people (DM split)
Route different WhatsApp DMs to different agents on one WhatsApp account by matching sender E.164 (+15551234567) with peer.kind: "direct". Replies still come from the same WhatsApp number — there is no per-agent sender identity.
Direct chats collapse to the agent’s main session key by default, so true isolation requires one agent per person.
Routing rules
Bindings are deterministic and most-specific wins. See Channel routing for the full tier order (exact peer, parent peer, peer wildcard, guild+roles, guild, team, account, channel, default agent). A few rules worth calling out here:- If multiple bindings match within the same tier, the first one in config order wins.
- If a binding sets multiple match fields (for example
peer+guildId), all specified fields must match (ANDsemantics). - A binding that omits
accountIdmatches only the default account, not every account. UseaccountId: "*"for a channel-wide fallback, oraccountId: "<name>"for one account. Adding the same binding again with an explicit account id upgrades the existing channel-only binding instead of duplicating it.
Multiple accounts / phone numbers
Channels that support multiple accounts (e.g. WhatsApp) useaccountId to identify each login. Each accountId routes to its own agent, so one server can host multiple phone numbers without mixing sessions.
Set channels.<channel>.defaultAccount to choose the account used when accountId is omitted. When unset, OpenClaw falls back to default if present, otherwise the first configured account id (sorted).
Channels supporting multiple accounts: discord, feishu, googlechat, imessage, irc, line, mattermost, matrix, nextcloud-talk, nostr, signal, slack, telegram, whatsapp, zalo, zalouser.
Concepts
agentId: one “brain” (workspace, per-agent auth, per-agent session store).accountId: one channel account instance (e.g. WhatsApp accountpersonalvsbiz).binding: routes inbound messages to anagentIdby(channel, accountId, peer), and optionally guild/team ids.- Direct chats collapse to
agent:<agentId>:<mainKey>(per-agent “main”; seesession.mainKey).
Platform examples
Discord bots per agent
Discord bots per agent
Each Discord bot account maps to a unique
accountId. Bind each account to an agent and keep allowlists per bot.- Invite each bot to the guild and enable Message Content Intent.
- Tokens live in
channels.discord.accounts.<id>.token(default account can useDISCORD_BOT_TOKEN).
Telegram bots per agent
Telegram bots per agent
- Create one bot per agent with BotFather and copy each token.
- Tokens live in
channels.telegram.accounts.<id>.botToken(default account can useTELEGRAM_BOT_TOKEN). - For multiple bots in the same Telegram group, invite each bot and mention the one that should answer.
- Disable BotFather Privacy Mode for each group bot (
/setprivacy-> Disable), then remove and re-add the bot so Telegram applies the setting. - Allow groups with
channels.telegram.groups, or usegroupPolicy: "open"only for trusted group deployments. - Put sender user IDs in
groupAllowFrom. Group and supergroup IDs belong inchannels.telegram.groups, notgroupAllowFrom. - Bind by
accountIdso each bot routes to its own agent.
WhatsApp numbers per agent
WhatsApp numbers per agent
Link each account before starting the gateway:
~/.openclaw/openclaw.json (JSON5):Common patterns
- WhatsApp daily + Telegram deep work
- Same channel, one peer to Opus
- Family agent bound to a WhatsApp group
Split by channel: route WhatsApp to a fast everyday agent and Telegram to an Opus agent.These examples use
accountId: "*" so the bindings keep working if you add accounts later. To route a single DM/group to Opus while keeping the rest on chat, add a match.peer binding for that peer — peer matches always win over channel-wide rules.Per-agent sandbox and tool configuration
Each agent can have its own sandbox and tool restrictions:setupCommand lives under sandbox.docker and runs once on container creation. Per-agent sandbox.docker.* overrides are ignored when the resolved scope is "shared".- Security isolation: restrict tools for untrusted agents.
- Resource control: sandbox specific agents while keeping others on host.
- Flexible policies: different permissions per agent.
tools.elevated has both a global gate (tools.elevated.enabled/allowFrom) and a per-agent gate (agents.list[].tools.elevated.enabled/allowFrom). The per-agent gate can only further restrict the global one — both must allow a sender for elevated commands to run. For group targeting, use agents.list[].groupChat.mentionPatterns so @mentions map cleanly to the intended agent.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