refactor!: rename chat providers to channels

This commit is contained in:
Peter Steinberger
2026-01-13 06:16:43 +00:00
parent 0cd632ba84
commit 90342a4f3a
393 changed files with 8004 additions and 6737 deletions

View File

@@ -219,6 +219,6 @@ Suggested `.gitignore` starter:
## Advanced notes
- Multi-agent routing can use different workspaces per agent. See
[Provider routing](/concepts/provider-routing) for routing configuration.
[Channel routing](/concepts/channel-routing) for routing configuration.
- If `agents.defaults.sandbox` is enabled, non-main sessions can use per-session sandbox
workspaces under `agents.defaults.sandbox.workspaceRoot`.

View File

@@ -102,7 +102,7 @@ More details: [Streaming + chunking](/concepts/streaming).
At minimum, set:
- `agents.defaults.workspace`
- `whatsapp.allowFrom` (strongly recommended)
- `channels.whatsapp.allowFrom` (strongly recommended)
---

View File

@@ -1,19 +1,19 @@
---
summary: "Routing rules per provider (WhatsApp, Telegram, Discord, Slack) and shared context"
summary: "Routing rules per channel (WhatsApp, Telegram, Discord, Slack) and shared context"
read_when:
- Changing provider routing or inbox behavior
- Changing channel routing or inbox behavior
---
# Providers & routing
# Channels & routing
Clawdbot routes replies **back to the provider where a message came from**. The
model does not choose a provider; routing is deterministic and controlled by the
Clawdbot 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.
## Key terms
- **Provider**: `whatsapp`, `telegram`, `discord`, `slack`, `signal`, `imessage`, `webchat`.
- **AccountId**: perprovider account instance (when supported).
- **Channel**: `whatsapp`, `telegram`, `discord`, `slack`, `signal`, `imessage`, `webchat`.
- **AccountId**: perchannel account instance (when supported).
- **AgentId**: an isolated workspace + session store (“brain”).
- **SessionKey**: the bucket key used to store context and control concurrency.
@@ -23,10 +23,10 @@ Direct messages collapse to the agents **main** session:
- `agent:<agentId>:<mainKey>` (default: `agent:main:main`)
Groups and channels remain isolated per provider:
Groups and channels remain isolated per channel:
- Groups: `agent:<agentId>:<provider>:group:<id>`
- Channels/rooms: `agent:<agentId>:<provider>:channel:<id>`
- Groups: `agent:<agentId>:<channel>:group:<id>`
- Channels/rooms: `agent:<agentId>:<channel>:channel:<id>`
Threads:
@@ -45,8 +45,8 @@ Routing picks **one agent** for each inbound message:
1. **Exact peer match** (`bindings` with `peer.kind` + `peer.id`).
2. **Guild match** (Discord) via `guildId`.
3. **Team match** (Slack) via `teamId`.
4. **Account match** (`accountId` on the provider).
5. **Provider match** (any account on that provider).
4. **Account match** (`accountId` on the channel).
5. **Channel match** (any account on that channel).
6. **Default agent** (`agents.list[].default`, else first list entry, fallback to `main`).
The matched agent determines which workspace and session store are used.
@@ -72,7 +72,7 @@ See: [Broadcast Groups](/broadcast-groups).
## Config overview
- `agents.list`: named agent definitions (workspace, model, etc.).
- `bindings`: map inbound providers/accounts/peers to agents.
- `bindings`: map inbound channels/accounts/peers to agents.
Example:
@@ -84,8 +84,8 @@ Example:
]
},
bindings: [
{ match: { provider: "slack", teamId: "T123" }, agentId: "support" },
{ match: { provider: "telegram", peer: { kind: "group", id: "-100123" } }, agentId: "support" }
{ match: { channel: "slack", teamId: "T123" }, agentId: "support" },
{ match: { channel: "telegram", peer: { kind: "group", id: "-100123" } }, agentId: "support" }
]
}
```
@@ -102,7 +102,7 @@ You can override the store path via `session.store` and `{agentId}` templating.
## WebChat behavior
WebChat attaches to the **selected agent** and defaults to the agents main
session. Because of this, WebChat lets you see crossprovider context for that
session. Because of this, WebChat lets you see crosschannel context for that
agent in one place.
## Reply context
@@ -111,4 +111,4 @@ Inbound replies include:
- `ReplyToId`, `ReplyToBody`, and `ReplyToSender` when available.
- Quoted context is appended to `Body` as a `[Replying to ...]` block.
This is consistent across providers.
This is consistent across channels.

View File

@@ -10,8 +10,8 @@ Goal: let Clawd sit in WhatsApp groups, wake up only when pinged, and keep that
Note: `agents.list[].groupChat.mentionPatterns` is now used by Telegram/Discord/Slack/iMessage as well; this doc focuses on WhatsApp-specific behavior. For multi-agent setups, set `agents.list[].groupChat.mentionPatterns` per agent (or use `messages.groupChat.mentionPatterns` as a global fallback).
## Whats implemented (2025-12-03)
- Activation modes: `mention` (default) or `always`. `mention` requires a ping (real WhatsApp @-mentions via `mentionedJids`, regex patterns, or the bots E.164 anywhere in the text). `always` wakes the agent on every message but it should reply only when it can add meaningful value; otherwise it returns the silent token `NO_REPLY`. Defaults can be set in config (`whatsapp.groups`) and overridden per group via `/activation`. When `whatsapp.groups` is set, it also acts as a group allowlist (include `"*"` to allow all).
- Group policy: `whatsapp.groupPolicy` controls whether group messages are accepted (`open|disabled|allowlist`). `allowlist` uses `whatsapp.groupAllowFrom` (fallback: explicit `whatsapp.allowFrom`). Default is `allowlist` (blocked until you add senders).
- Activation modes: `mention` (default) or `always`. `mention` requires a ping (real WhatsApp @-mentions via `mentionedJids`, regex patterns, or the bots E.164 anywhere in the text). `always` wakes the agent on every message but it should reply only when it can add meaningful value; otherwise it returns the silent token `NO_REPLY`. Defaults can be set in config (`channels.whatsapp.groups`) and overridden per group via `/activation`. When `channels.whatsapp.groups` is set, it also acts as a group allowlist (include `"*"` to allow all).
- Group policy: `channels.whatsapp.groupPolicy` controls whether group messages are accepted (`open|disabled|allowlist`). `allowlist` uses `channels.whatsapp.groupAllowFrom` (fallback: explicit `channels.whatsapp.allowFrom`). Default is `allowlist` (blocked until you add senders).
- Per-group sessions: session keys look like `agent:<agentId>:whatsapp:group:<jid>` so commands such as `/verbose on` or `/think high` (sent as standalone messages) are scoped to that group; personal DM state is untouched. Heartbeats are skipped for group threads.
- Context injection: last N (default 50) group messages are prefixed under `[Chat messages since your last reply - for context]`, with the triggering line under `[Current message - respond to this]`.
- Sender surfacing: every group batch now ends with `[from: Sender Name (+E164)]` so Pi knows who is speaking.
@@ -57,7 +57,7 @@ Use the group chat command:
- `/activation mention`
- `/activation always`
Only the owner number (from `whatsapp.allowFrom`, or the bots own E.164 when unset) can change this. Send `/status` as a standalone message in the group to see the current activation mode.
Only the owner number (from `channels.whatsapp.allowFrom`, or the bots own E.164 when unset) can change this. Send `/status` as a standalone message in the group to see the current activation mode.
## How to use
1) Add Clawd UK (`+447700900123`) to the group.

View File

@@ -55,35 +55,37 @@ Control how group/room messages are handled per provider:
```json5
{
whatsapp: {
groupPolicy: "disabled", // "open" | "disabled" | "allowlist"
groupAllowFrom: ["+15551234567"]
},
telegram: {
groupPolicy: "disabled",
groupAllowFrom: ["123456789", "@username"]
},
signal: {
groupPolicy: "disabled",
groupAllowFrom: ["+15551234567"]
},
imessage: {
groupPolicy: "disabled",
groupAllowFrom: ["chat_id:123"]
},
msteams: {
groupPolicy: "disabled",
groupAllowFrom: ["user@org.com"]
},
discord: {
groupPolicy: "allowlist",
guilds: {
"GUILD_ID": { channels: { help: { allow: true } } }
channels: {
whatsapp: {
groupPolicy: "disabled", // "open" | "disabled" | "allowlist"
groupAllowFrom: ["+15551234567"]
},
telegram: {
groupPolicy: "disabled",
groupAllowFrom: ["123456789", "@username"]
},
signal: {
groupPolicy: "disabled",
groupAllowFrom: ["+15551234567"]
},
imessage: {
groupPolicy: "disabled",
groupAllowFrom: ["chat_id:123"]
},
msteams: {
groupPolicy: "disabled",
groupAllowFrom: ["user@org.com"]
},
discord: {
groupPolicy: "allowlist",
guilds: {
"GUILD_ID": { channels: { help: { allow: true } } }
}
},
slack: {
groupPolicy: "allowlist",
channels: { "#general": { allow: true } }
}
},
slack: {
groupPolicy: "allowlist",
channels: { "#general": { allow: true } }
}
}
```
@@ -97,9 +99,9 @@ Control how group/room messages are handled per provider:
Notes:
- `groupPolicy` is separate from mention-gating (which requires @mentions).
- WhatsApp/Telegram/Signal/iMessage/Microsoft Teams: use `groupAllowFrom` (fallback: explicit `allowFrom`).
- Discord: allowlist uses `discord.guilds.<id>.channels`.
- Slack: allowlist uses `slack.channels`.
- Group DMs are controlled separately (`discord.dm.*`, `slack.dm.*`).
- Discord: allowlist uses `channels.discord.guilds.<id>.channels`.
- Slack: allowlist uses `channels.slack.channels`.
- Group DMs are controlled separately (`channels.discord.dm.*`, `channels.slack.dm.*`).
- Telegram allowlist can match user IDs (`"123456789"`, `"telegram:123456789"`, `"tg:123456789"`) or usernames (`"@alice"` or `"alice"`); prefixes are case-insensitive.
- Default is `groupPolicy: "allowlist"`; if your group allowlist is empty, group messages are blocked.
@@ -113,22 +115,24 @@ Group messages require a mention unless overridden per group. Defaults live per
```json5
{
whatsapp: {
groups: {
"*": { requireMention: true },
"123@g.us": { requireMention: false }
}
},
telegram: {
groups: {
"*": { requireMention: true },
"123456789": { requireMention: false }
}
},
imessage: {
groups: {
"*": { requireMention: true },
"123": { requireMention: false }
channels: {
whatsapp: {
groups: {
"*": { requireMention: true },
"123@g.us": { requireMention: false }
}
},
telegram: {
groups: {
"*": { requireMention: true },
"123456789": { requireMention: false }
}
},
imessage: {
groups: {
"*": { requireMention: true },
"123": { requireMention: false }
}
}
},
agents: {
@@ -150,28 +154,30 @@ Notes:
- Surfaces that provide explicit mentions still pass; patterns are a fallback.
- Per-agent override: `agents.list[].groupChat.mentionPatterns` (useful when multiple agents share a group).
- Mention gating is only enforced when mention detection is possible (native mentions or `mentionPatterns` are configured).
- Discord defaults live in `discord.guilds."*"` (overridable per guild/channel).
- Discord defaults live in `channels.discord.guilds."*"` (overridable per guild/channel).
- Group history context is wrapped uniformly across providers; use `messages.groupChat.historyLimit` for the global default and `<provider>.historyLimit` (or `<provider>.accounts.*.historyLimit`) for overrides. Set `0` to disable.
## Group allowlists
When `whatsapp.groups`, `telegram.groups`, or `imessage.groups` is configured, the keys act as a group allowlist. Use `"*"` to allow all groups while still setting default mention behavior.
When `channels.whatsapp.groups`, `channels.telegram.groups`, or `channels.imessage.groups` is configured, the keys act as a group allowlist. Use `"*"` to allow all groups while still setting default mention behavior.
Common intents (copy/paste):
1) Disable all group replies
```json5
{
whatsapp: { groupPolicy: "disabled" }
channels: { whatsapp: { groupPolicy: "disabled" } }
}
```
2) Allow only specific groups (WhatsApp)
```json5
{
whatsapp: {
groups: {
"123@g.us": { requireMention: true },
"456@g.us": { requireMention: false }
channels: {
whatsapp: {
groups: {
"123@g.us": { requireMention: true },
"456@g.us": { requireMention: false }
}
}
}
}
@@ -180,8 +186,10 @@ Common intents (copy/paste):
3) Allow all groups but require mention (explicit)
```json5
{
whatsapp: {
groups: { "*": { requireMention: true } }
channels: {
whatsapp: {
groups: { "*": { requireMention: true } }
}
}
}
```
@@ -189,10 +197,12 @@ Common intents (copy/paste):
4) Only the owner can trigger in groups (WhatsApp)
```json5
{
whatsapp: {
groupPolicy: "allowlist",
groupAllowFrom: ["+15551234567"],
groups: { "*": { requireMention: true } }
channels: {
whatsapp: {
groupPolicy: "allowlist",
groupAllowFrom: ["+15551234567"],
groups: { "*": { requireMention: true } }
}
}
}
```
@@ -202,7 +212,7 @@ Group owners can toggle per-group activation:
- `/activation mention`
- `/activation always`
Owner is determined by `whatsapp.allowFrom` (or the bots self E.164 when unset). Send the command as a standalone message. Other surfaces currently ignore `/activation`.
Owner is determined by `channels.whatsapp.allowFrom` (or the bots self E.164 when unset). Send the command as a standalone message. Other surfaces currently ignore `/activation`.
## Context fields
Group inbound payloads set:

View File

@@ -23,7 +23,7 @@ Inbound message
Key knobs live in configuration:
- `messages.*` for prefixes, queueing, and group behavior.
- `agents.defaults.*` for block streaming and chunking defaults.
- Provider overrides (`whatsapp.*`, `telegram.*`, etc.) for caps and streaming toggles.
- Provider overrides (`channels.whatsapp.*`, `channels.telegram.*`, etc.) for caps and streaming toggles.
See [Configuration](/gateway/configuration) for full schema.
@@ -63,8 +63,8 @@ Directive stripping only applies to the **current message** section so history
remains intact. Providers that wrap history should set `CommandBody` (or
`RawBody`) to the original message text and keep `Body` as the combined prompt.
History buffers are configurable via `messages.groupChat.historyLimit` (global
default) and per-provider overrides like `slack.historyLimit` or
`telegram.accounts.<id>.historyLimit` (set `0` to disable).
default) and per-provider overrides like `channels.slack.historyLimit` or
`channels.telegram.accounts.<id>.historyLimit` (set `0` to disable).
## Queueing and followups
@@ -103,7 +103,7 @@ Details: [Thinking + reasoning directives](/tools/thinking) and [Token use](/tok
## Prefixes, threading, and replies
Outbound message formatting is centralized in `messages`:
- `messages.responsePrefix` (outbound prefix) and `whatsapp.messagePrefix` (WhatsApp inbound prefix)
- `messages.responsePrefix` (outbound prefix) and `channels.whatsapp.messagePrefix` (WhatsApp inbound prefix)
- Reply threading via `replyToMode` and per-provider defaults
Details: [Configuration](/gateway/configuration#messages) and provider docs.

View File

@@ -90,9 +90,11 @@ Example:
{ agentId: "alex", match: { provider: "whatsapp", peer: { kind: "dm", id: "+15551230001" } } },
{ agentId: "mia", match: { provider: "whatsapp", peer: { kind: "dm", id: "+15551230002" } } }
],
whatsapp: {
dmPolicy: "allowlist",
allowFrom: ["+15551230001", "+15551230002"]
channels: {
whatsapp: {
dmPolicy: "allowlist",
allowFrom: ["+15551230001", "+15551230002"]
}
}
}
```
@@ -173,15 +175,17 @@ multiple phone numbers without mixing sessions.
},
},
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",
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",
},
},
},
},

View File

@@ -56,13 +56,13 @@ How to verify:
```bash
clawdbot models status
clawdbot providers list
clawdbot channels list
```
Or JSON:
```bash
clawdbot providers list --json
clawdbot channels list --json
```
## OAuth exchange (how login works)
@@ -148,7 +148,7 @@ Example (session override):
- `/model Opus@anthropic:work`
How to see what profile IDs exist:
- `clawdbot providers list --json` (shows `auth[]`)
- `clawdbot channels list --json` (shows `auth[]`)
Related docs:
- [/concepts/model-failover](/concepts/model-failover) (rotation + cooldown rules)

View File

@@ -34,20 +34,22 @@ Set retry policy per provider in `~/.clawdbot/clawdbot.json`:
```json5
{
telegram: {
retry: {
attempts: 3,
minDelayMs: 400,
maxDelayMs: 30000,
jitter: 0.1
}
},
discord: {
retry: {
attempts: 3,
minDelayMs: 500,
maxDelayMs: 30000,
jitter: 0.1
channels: {
telegram: {
retry: {
attempts: 3,
minDelayMs: 400,
maxDelayMs: 30000,
jitter: 0.1
}
},
discord: {
retry: {
attempts: 3,
minDelayMs: 500,
maxDelayMs: 30000,
jitter: 0.1
}
}
}
}

View File

@@ -37,8 +37,8 @@ Legend:
- `agents.defaults.blockStreamingBreak`: `"text_end"` or `"message_end"`.
- `agents.defaults.blockStreamingChunk`: `{ minChars, maxChars, breakPreference? }`.
- `agents.defaults.blockStreamingCoalesce`: `{ minChars?, maxChars?, idleMs? }` (merge streamed blocks before send).
- Provider hard cap: `*.textChunkLimit` (e.g., `whatsapp.textChunkLimit`).
- Discord soft cap: `discord.maxLinesPerMessage` (default 17) splits tall replies to avoid UI clipping.
- Provider hard cap: `*.textChunkLimit` (e.g., `channels.whatsapp.textChunkLimit`).
- Discord soft cap: `channels.discord.maxLinesPerMessage` (default 17) splits tall replies to avoid UI clipping.
**Boundary semantics:**
- `text_end`: stream blocks as soon as chunker emits; flush on each `text_end`.
@@ -90,7 +90,7 @@ This maps to:
**Provider note:** For non-Telegram providers, block streaming is **off unless**
`*.blockStreaming` is explicitly set to `true`. Telegram can stream drafts
(`telegram.streamMode`) without block replies.
(`channels.telegram.streamMode`) without block replies.
Config location reminder: the `blockStreaming*` defaults live under
`agents.defaults`, not the root config.
@@ -99,11 +99,11 @@ Config location reminder: the `blockStreaming*` defaults live under
Telegram is the only provider with draft streaming:
- Uses Bot API `sendMessageDraft` in **private chats with topics**.
- `telegram.streamMode: "partial" | "block" | "off"`.
- `channels.telegram.streamMode: "partial" | "block" | "off"`.
- `partial`: draft updates with the latest stream text.
- `block`: draft updates in chunked blocks (same chunker rules).
- `off`: no draft streaming.
- Draft chunk config (only for `streamMode: "block"`): `telegram.draftChunk` (defaults: `minChars: 200`, `maxChars: 800`).
- Draft chunk config (only for `streamMode: "block"`): `channels.telegram.draftChunk` (defaults: `minChars: 200`, `maxChars: 800`).
- Draft streaming is separate from block streaming; block replies are off by default and only enabled by `*.blockStreaming: true` on non-Telegram providers.
- Final reply is still a normal message.
- `/reasoning stream` writes reasoning into the draft bubble (Telegram only).

View File

@@ -21,7 +21,7 @@ The timestamp in the envelope is **always UTC**, with minutes precision.
## Tool payloads (raw provider data)
Tool calls (`discord.readMessages`, `slack.readMessages`, etc.) return **raw provider timestamps**.
Tool calls (`channels.discord.readMessages`, `channels.slack.readMessages`, etc.) return **raw provider timestamps**.
These are typically UTC ISO strings (Discord) or UTC epoch strings (Slack). We do not rewrite them.
## User timezone for the system prompt

View File

@@ -14,7 +14,7 @@ read_when:
- `/status` in chats: emojirich status card with session tokens + estimated cost (API key only) and provider quota windows when available.
- `/cost on|off` in chats: toggles perresponse usage lines (OAuth shows tokens only).
- CLI: `clawdbot status --usage` prints a full per-provider breakdown.
- CLI: `clawdbot providers list` prints the same usage snapshot alongside provider config (use `--no-usage` to skip).
- CLI: `clawdbot channels list` prints the same usage snapshot alongside provider config (use `--no-usage` to skip).
- macOS menu bar: “Usage” section under Context (only if available).
## Providers + credentials