docs/channels/channel-routing.md
OpenClaw routes replies back to the channel where a message came from. The model does not choose a channel; routing is deterministic and controlled by the host configuration.
telegram, whatsapp, discord, irc, googlechat, slack, signal, imessage, line, plus plugin channels. webchat is the internal WebChat UI channel and is not a configurable outbound channel.channels.<channel>.defaultAccount chooses
which account is used when an outbound path does not specify accountId.
defaultAccount or accounts.default) when two or more accounts are configured. Without it, fallback routing may pick the first normalized account ID.Explicit outbound targets may include a provider prefix, such as telegram:123 or tg:123. Core treats that prefix as a channel-selection hint only when the selected channel is last or otherwise unresolved, and only when the loaded plugin advertises that prefix. If the caller already selected an explicit channel, the provider prefix must match that channel; cross-channel combinations such as WhatsApp delivery to telegram:123 fail before plugin-specific target normalization.
Target-kind and service prefixes such as channel:<id>, user:<id>, room:<id>, thread:<id>, imessage:<handle>, and sms:<number> stay inside the selected channel's grammar. They do not select the provider by themselves.
Direct messages collapse to the agent’s main session by default:
agent:<agentId>:<mainKey> (default: agent:main:main)Even when direct-message conversation history is shared with main, sandbox and tool policy use a derived per-account direct-chat runtime key for external DMs so channel-originated messages are not treated like local main-session runs.
Groups and channels remain isolated per channel:
agent:<agentId>:<channel>:group:<id>agent:<agentId>:<channel>:channel:<id>Threads:
:thread:<threadId> to the base key.:topic:<topicId> in the group key.Examples:
agent:main:telegram:group:-1001234567890:topic:42agent:main:discord:channel:123456:thread:987654When session.dmScope is main, direct messages may share one main session.
To prevent the session’s lastRoute from being overwritten by non-owner DMs,
OpenClaw infers a pinned owner from allowFrom when all of these are true:
allowFrom has exactly one non-wildcard entry.In that mismatch case, OpenClaw still records inbound session metadata, but it
skips updating the main session lastRoute.
Channel plugins can mark an inbound session record as createIfMissing: false
when a guarded path must not create a new OpenClaw session. In that mode,
OpenClaw may update metadata and lastRoute for an existing session, but it
does not create a route-only session entry just because a message was observed.
Routing picks one agent for each inbound message:
bindings with peer.kind + peer.id).guildId + roles.guildId.teamId.accountId on the channel).accountId: "*").agents.list[].default, else first list entry, fallback to main).When a binding includes multiple match fields (peer, guildId, teamId, roles), all provided fields must match for that binding to apply.
The matched agent determines which workspace and session store are used.
Broadcast groups let you run multiple agents for the same peer when OpenClaw would normally reply (for example: in WhatsApp groups, after mention/activation gating).
Config:
{
broadcast: {
strategy: "parallel",
"[email protected]": ["alfred", "baerbel"],
"+15555550123": ["support", "logger"],
},
}
See: Broadcast Groups.
agents.list: named agent definitions (workspace, model, etc.).bindings: map inbound channels/accounts/peers to agents.Example:
{
agents: {
list: [{ id: "support", name: "Support", workspace: "~/.openclaw/workspace-support" }],
},
bindings: [
{ match: { channel: "slack", teamId: "T123" }, agentId: "support" },
{ match: { channel: "telegram", peer: { kind: "group", id: "-100123" } }, agentId: "support" },
],
}
Session stores live under the state directory (default ~/.openclaw):
~/.openclaw/agents/<agentId>/sessions/sessions.jsonYou can override the store path via session.store and {agentId} templating.
Gateway and ACP session discovery also scans disk-backed agent stores under the
default agents/ root and under templated session.store roots. Discovered
stores must stay inside that resolved agent root and use a regular
sessions.json file. Symlinks and out-of-root paths are ignored.
WebChat attaches to the selected agent and defaults to the agent’s main session. Because of this, WebChat lets you see cross‑channel context for that agent in one place.
Inbound replies include:
ReplyToId, ReplyToBody, and ReplyToSender when available.Body as a [Replying to ...] block.This is consistent across channels.