309 lines
9.7 KiB
Markdown
309 lines
9.7 KiB
Markdown
---
|
||
summary: "Multi-agent routing: isolated agents, channel accounts, and bindings"
|
||
title: Multi-Agent Routing
|
||
read_when: "You want multiple isolated agents (workspaces + auth) in one gateway process."
|
||
status: active
|
||
---
|
||
|
||
# Multi-Agent Routing
|
||
|
||
Goal: multiple *isolated* agents (separate workspace + `agentDir` + sessions), plus multiple channel accounts (e.g. two WhatsApps) in one running Gateway. Inbound is routed to an agent via bindings.
|
||
|
||
## What is “one agent”?
|
||
|
||
An **agent** is a fully scoped brain with its own:
|
||
|
||
- **Workspace** (files, AGENTS.md/SOUL.md/USER.md, local notes, persona rules).
|
||
- **State directory** (`agentDir`) for auth profiles, model registry, and per-agent config.
|
||
- **Session store** (chat history + routing state) under `~/.clawdbot/agents/<agentId>/sessions`.
|
||
|
||
Auth profiles are **per-agent**. Each agent reads from its own:
|
||
|
||
```
|
||
~/.clawdbot/agents/<agentId>/agent/auth-profiles.json
|
||
```
|
||
|
||
Main agent credentials are **not** shared automatically. Never reuse `agentDir`
|
||
across agents (it causes auth/session collisions). If you want to share creds,
|
||
copy `auth-profiles.json` into the other agent's `agentDir`.
|
||
|
||
Skills are per-agent via each workspace’s `skills/` folder, with shared skills
|
||
available from `~/.clawdbot/skills`. See [Skills: per-agent vs shared](/tools/skills#per-agent-vs-shared-skills).
|
||
|
||
The Gateway can host **one agent** (default) or **many agents** side-by-side.
|
||
|
||
**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](/gateway/sandboxing).
|
||
|
||
## Paths (quick map)
|
||
|
||
- Config: `~/.clawdbot/clawdbot.json` (or `CLAWDBOT_CONFIG_PATH`)
|
||
- State dir: `~/.clawdbot` (or `CLAWDBOT_STATE_DIR`)
|
||
- Workspace: `~/clawd` (or `~/clawd-<agentId>`)
|
||
- Agent dir: `~/.clawdbot/agents/<agentId>/agent` (or `agents.list[].agentDir`)
|
||
- Sessions: `~/.clawdbot/agents/<agentId>/sessions`
|
||
|
||
### Single-agent mode (default)
|
||
|
||
If you do nothing, Clawdbot runs a single agent:
|
||
|
||
- `agentId` defaults to **`main`**.
|
||
- Sessions are keyed as `agent:main:<mainKey>`.
|
||
- Workspace defaults to `~/clawd` (or `~/clawd-<profile>` when `CLAWDBOT_PROFILE` is set).
|
||
- State defaults to `~/.clawdbot/agents/main/agent`.
|
||
|
||
## Agent helper
|
||
|
||
Use the agent wizard to add a new isolated agent:
|
||
|
||
```bash
|
||
clawdbot agents add work
|
||
```
|
||
|
||
Then add `bindings` (or let the wizard do it) to route inbound messages.
|
||
|
||
Verify with:
|
||
|
||
```bash
|
||
clawdbot agents list --bindings
|
||
```
|
||
|
||
## Multiple agents = multiple people, multiple personalities
|
||
|
||
With **multiple agents**, each `agentId` becomes a **fully isolated persona**:
|
||
|
||
- **Different phone numbers/accounts** (per channel `accountId`).
|
||
- **Different personalities** (per-agent workspace files like `AGENTS.md` and `SOUL.md`).
|
||
- **Separate auth + sessions** (no cross-talk unless explicitly enabled).
|
||
|
||
This lets **multiple people** share one Gateway server while keeping their AI “brains” and data isolated.
|
||
|
||
## One WhatsApp number, multiple people (DM split)
|
||
|
||
You can route **different WhatsApp DMs** to different agents while staying on **one WhatsApp account**. Match on sender E.164 (like `+15551234567`) with `peer.kind: "dm"`. Replies still come from the same WhatsApp number (no per‑agent sender identity).
|
||
|
||
Important detail: direct chats collapse to the agent’s **main session key**, so true isolation requires **one agent per person**.
|
||
|
||
Example:
|
||
|
||
```json5
|
||
{
|
||
agents: {
|
||
list: [
|
||
{ id: "alex", workspace: "~/clawd-alex" },
|
||
{ id: "mia", workspace: "~/clawd-mia" }
|
||
]
|
||
},
|
||
bindings: [
|
||
{ agentId: "alex", match: { channel: "whatsapp", peer: { kind: "dm", id: "+15551230001" } } },
|
||
{ agentId: "mia", match: { channel: "whatsapp", peer: { kind: "dm", id: "+15551230002" } } }
|
||
],
|
||
channels: {
|
||
whatsapp: {
|
||
dmPolicy: "allowlist",
|
||
allowFrom: ["+15551230001", "+15551230002"]
|
||
}
|
||
}
|
||
}
|
||
```
|
||
|
||
Notes:
|
||
- DM access control is **global per WhatsApp account** (pairing/allowlist), not per agent.
|
||
- For shared groups, bind the group to one agent or use [Broadcast groups](/broadcast-groups).
|
||
|
||
## Routing rules (how messages pick an agent)
|
||
|
||
Bindings are **deterministic** and **most-specific wins**:
|
||
|
||
1. `peer` match (exact DM/group/channel id)
|
||
2. `guildId` (Discord)
|
||
3. `teamId` (Slack)
|
||
4. `accountId` match for a channel
|
||
5. channel-level match (`accountId: "*"`)
|
||
6. fallback to default agent (`agents.list[].default`, else first list entry, default: `main`)
|
||
|
||
## Multiple accounts / phone numbers
|
||
|
||
Channels that support **multiple accounts** (e.g. WhatsApp) use `accountId` to identify
|
||
each login. Each `accountId` can be routed to a different agent, so one server can host
|
||
multiple phone numbers without mixing sessions.
|
||
|
||
## Concepts
|
||
|
||
- `agentId`: one “brain” (workspace, per-agent auth, per-agent session store).
|
||
- `accountId`: one channel account instance (e.g. WhatsApp account `"personal"` vs `"biz"`).
|
||
- `binding`: routes inbound messages to an `agentId` by `(channel, accountId, peer)` and optionally guild/team ids.
|
||
- Direct chats collapse to `agent:<agentId>:<mainKey>` (per-agent “main”; `session.mainKey`).
|
||
|
||
## Example: two WhatsApps → two agents
|
||
|
||
`~/.clawdbot/clawdbot.json` (JSON5):
|
||
|
||
```js
|
||
{
|
||
agents: {
|
||
list: [
|
||
{
|
||
id: "home",
|
||
default: true,
|
||
name: "Home",
|
||
workspace: "~/clawd-home",
|
||
agentDir: "~/.clawdbot/agents/home/agent",
|
||
},
|
||
{
|
||
id: "work",
|
||
name: "Work",
|
||
workspace: "~/clawd-work",
|
||
agentDir: "~/.clawdbot/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: ~/.clawdbot/credentials/whatsapp/personal
|
||
// authDir: "~/.clawdbot/credentials/whatsapp/personal",
|
||
},
|
||
biz: {
|
||
// Optional override. Default: ~/.clawdbot/credentials/whatsapp/biz
|
||
// authDir: "~/.clawdbot/credentials/whatsapp/biz",
|
||
},
|
||
},
|
||
},
|
||
},
|
||
}
|
||
```
|
||
|
||
## Example: WhatsApp daily chat + Telegram deep work
|
||
|
||
Split by channel: route WhatsApp to a fast everyday agent and Telegram to an Opus agent.
|
||
|
||
```json5
|
||
{
|
||
agents: {
|
||
list: [
|
||
{
|
||
id: "chat",
|
||
name: "Everyday",
|
||
workspace: "~/clawd-chat",
|
||
model: "anthropic/claude-sonnet-4-5"
|
||
},
|
||
{
|
||
id: "opus",
|
||
name: "Deep Work",
|
||
workspace: "~/clawd-opus",
|
||
model: "anthropic/claude-opus-4-5"
|
||
}
|
||
]
|
||
},
|
||
bindings: [
|
||
{ agentId: "chat", match: { channel: "whatsapp" } },
|
||
{ agentId: "opus", match: { channel: "telegram" } }
|
||
]
|
||
}
|
||
```
|
||
|
||
Notes:
|
||
- If you have multiple accounts for a channel, add `accountId` to the binding (for example `{ channel: "whatsapp", accountId: "personal" }`).
|
||
- 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.
|
||
|
||
## Example: same channel, one peer to Opus
|
||
|
||
Keep WhatsApp on the fast agent, but route one DM to Opus:
|
||
|
||
```json5
|
||
{
|
||
agents: {
|
||
list: [
|
||
{ id: "chat", name: "Everyday", workspace: "~/clawd-chat", model: "anthropic/claude-sonnet-4-5" },
|
||
{ id: "opus", name: "Deep Work", workspace: "~/clawd-opus", model: "anthropic/claude-opus-4-5" }
|
||
]
|
||
},
|
||
bindings: [
|
||
{ agentId: "opus", match: { channel: "whatsapp", peer: { kind: "dm", id: "+15551234567" } } },
|
||
{ agentId: "chat", match: { channel: "whatsapp" } }
|
||
]
|
||
}
|
||
```
|
||
|
||
Peer bindings always win, so keep them above the channel-wide rule.
|
||
|
||
## Per-Agent Sandbox and Tool Configuration
|
||
|
||
Starting with v2026.1.6, each agent can have its own sandbox and tool restrictions:
|
||
|
||
```js
|
||
{
|
||
agents: {
|
||
list: [
|
||
{
|
||
id: "personal",
|
||
workspace: "~/clawd-personal",
|
||
sandbox: {
|
||
mode: "off", // No sandbox for personal agent
|
||
},
|
||
// No tool restrictions - all tools available
|
||
},
|
||
{
|
||
id: "family",
|
||
workspace: "~/clawd-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: `setupCommand` lives under `sandbox.docker` and runs once on container creation.
|
||
Per-agent `sandbox.docker.*` overrides are ignored when the resolved scope is `"shared"`.
|
||
|
||
**Benefits:**
|
||
- **Security isolation**: Restrict tools for untrusted agents
|
||
- **Resource control**: Sandbox specific agents while keeping others on host
|
||
- **Flexible policies**: Different permissions per agent
|
||
|
||
Note: `tools.elevated` is **global** and sender-based; it is not configurable per agent.
|
||
If you need per-agent boundaries, use `agents.list[].tools` to deny `exec`.
|
||
For group targeting, use `agents.list[].groupChat.mentionPatterns` so @mentions map cleanly to the intended agent.
|
||
|
||
See [Multi-Agent Sandbox & Tools](/multi-agent-sandbox-tools) for detailed examples.
|