docs(channels): document bot loop protection

This commit is contained in:
Peter Steinberger
2026-05-13 13:34:09 +01:00
parent 4785a073d6
commit 5ac6b600de
9 changed files with 216 additions and 0 deletions

View File

@@ -179,6 +179,7 @@ Docs: https://docs.openclaw.ai
- CLI: strip generic OSC terminal escape payloads from sanitized output fields, preventing clipboard/title escape bodies from leaking into commitment tables and other terminal-safe text. Thanks @shakkernerd.
- Codex app-server: match connector-backed plugin approval elicitations by stable connector id so enabled destructive actions no longer fall through to display-name-only rejection.
- Build: replace selected build utility `tsx` preloads with Node native type stripping so Node 26 build paths no longer emit `DEP0205` module loader deprecation warnings. (#78584) Thanks @keshavbotagent.
- Channels/loop-guard: enforce shared per-pair bot loop protection in the core channel-turn kernel, with Discord, Slack, Matrix, and Google Chat supplying bot-pair facts where they can reliably identify accepted bot-authored messages. The generic guard keys on `(scope, conversation, participant pair)`, suppresses every additional bot-to-bot event in either direction once a pair crosses the configured budget, and lifts suppression after `cooldownSeconds`. Defaults are `maxEventsPerWindow: 20`, `windowSeconds: 60`, and `cooldownSeconds: 60` whenever a channel lets bot-authored messages reach dispatch; they can be set globally via `channels.defaults.botLoopProtection` and overridden per channel/account or supported per-conversation config. Fixes #58789. Thanks @pandadev66.
- Media generation: honor configured music and video generation timeouts when tool calls omit `timeoutMs`, matching image generation behavior. (#80687)
- CLI/update/status: label beta-channel plugin fallback and model-pricing refresh failures as warnings, keeping mixed beta/latest plugin cohorts visible without making core update or Gateway reachability look failed. Fixes #80689. Thanks @BKF-Gitty.
- Doctor/plugins: relink managed npm plugin `openclaw` peer dependencies during `doctor --fix`, while refusing to follow package-local `node_modules` symlinks outside the plugin package. (#77412) Thanks @TheCrazyLex.

View File

@@ -0,0 +1,131 @@
---
summary: "Bot-to-bot loop protection defaults and channel overrides"
read_when:
- Configuring bot-authored channel messages
- Tuning bot-to-bot loop protection
title: "Bot loop protection"
sidebarTitle: "Bot loop protection"
---
# Bot loop protection
OpenClaw can accept messages written by other bots on channels that support `allowBots`.
When that path is enabled, pair loop protection prevents two bot identities from
replying to each other indefinitely.
The guard is enforced by the core channel-turn kernel. Each supporting channel
maps its own inbound event into generic facts: account or scope, conversation id,
sender bot id, and receiver bot id. Core then tracks the participant pair in both
directions, applies a sliding-window budget, and suppresses the pair during a
cooldown after the budget is exceeded.
## Defaults
Pair loop protection is active when a channel lets bot-authored messages reach
dispatch. Built-in defaults are:
- `maxEventsPerWindow: 20` - a bot pair can exchange 20 events within the window
- `windowSeconds: 60` - sliding window length
- `cooldownSeconds: 60` - suppression time after the pair exceeds the budget
The guard does not affect normal human-authored messages, single-bot deployments,
self-message filtering, or one-shot bot replies that stay under the budget.
## Configure shared defaults
Set `channels.defaults.botLoopProtection` once to give every supporting channel
the same baseline. Channel and account overrides can still tune individual
surfaces.
```json5
{
channels: {
defaults: {
botLoopProtection: {
maxEventsPerWindow: 20,
windowSeconds: 60,
cooldownSeconds: 60,
},
},
},
}
```
Set `enabled: false` only when your channel policy intentionally allows
bot-to-bot conversations without automatic suppression.
## Override per channel or account
Supporting channels layer their own config over the shared default. Precedence is:
- `channels.<channel>.<room-or-space>.botLoopProtection`, when the channel supports per-conversation overrides
- `channels.<channel>.accounts.<account>.botLoopProtection`, when the channel supports accounts
- `channels.<channel>.botLoopProtection`, when the channel supports top-level defaults
- `channels.defaults.botLoopProtection`
- built-in defaults
```json5
{
channels: {
defaults: {
botLoopProtection: {
maxEventsPerWindow: 20,
},
},
discord: {
botLoopProtection: {
maxEventsPerWindow: 8,
},
accounts: {
molty: {
allowBots: "mentions",
botLoopProtection: {
maxEventsPerWindow: 5,
cooldownSeconds: 90,
},
},
},
},
slack: {
allowBots: "mentions",
botLoopProtection: {
maxEventsPerWindow: 8,
},
},
matrix: {
allowBots: "mentions",
groups: {
"!roomid:example.org": {
botLoopProtection: {
maxEventsPerWindow: 5,
},
},
},
},
googlechat: {
allowBots: true,
groups: {
"spaces/AAAA": {
botLoopProtection: {
maxEventsPerWindow: 5,
},
},
},
},
},
}
```
## Channel support
- Discord: native `author.bot` facts, keyed by Discord account, channel, and bot pair.
- Slack: native `bot_id` facts for accepted bot-authored messages, keyed by Slack account, channel, and bot pair.
- Matrix: configured Matrix bot accounts, keyed by Matrix account, room, and configured bot pair.
- Google Chat: native `sender.type=BOT` facts for accepted bot-authored messages, keyed by account, space, and bot pair.
Channels that do not expose a reliable inbound bot identity keep using their
normal self-message and access-policy filters. They should not opt into this
guard until they can identify both participants in the bot pair.
See [SDK runtime](/plugins/sdk-runtime#reusable-runtime-utilities) for plugin
implementation details.

View File

@@ -1569,10 +1569,39 @@ openclaw logs --follow
If you set `channels.discord.allowBots=true`, use strict mention and allowlist rules to avoid loop behavior.
Prefer `channels.discord.allowBots="mentions"` to only accept bot messages that mention the bot.
OpenClaw also ships shared [bot loop protection](/channels/bot-loop-protection). Whenever `allowBots` lets bot-authored messages reach dispatch, Discord maps the inbound event to `(account, channel, bot pair)` facts and the generic pair guard suppresses the pair after it crosses the configured event budget. The guard prevents runaway two-bot loops that previously had to be stopped by Discord rate limits; it does not affect single-bot deployments or one-shot bot replies that stay under the budget.
Default settings (active when `allowBots` is set):
- `maxEventsPerWindow: 20` -- bot pair can exchange 20 messages within the sliding window
- `windowSeconds: 60` -- sliding window length
- `cooldownSeconds: 60` -- once the budget trips, every additional bot-to-bot message in either direction is dropped for one minute
Configure the shared default once under `channels.defaults.botLoopProtection`, then override Discord when a legitimate workflow needs more headroom. Precedence is:
- `channels.discord.accounts.<account>.botLoopProtection`
- `channels.discord.botLoopProtection`
- `channels.defaults.botLoopProtection`
- built-in defaults
Discord uses the generic `maxEventsPerWindow`, `windowSeconds`, and `cooldownSeconds` keys.
```json5
{
channels: {
defaults: {
botLoopProtection: {
maxEventsPerWindow: 20,
windowSeconds: 60,
cooldownSeconds: 60,
},
},
discord: {
// Optional Discord-wide override. Account blocks override individual
// fields and inherit omitted fields from here.
botLoopProtection: {
maxEventsPerWindow: 4,
},
accounts: {
mantis: {
// Mantis listens to other bots only when they mention her.
@@ -1585,6 +1614,12 @@ openclaw logs --follow
// Lets Molty write "@Mantis" and send a real Discord mention.
Mantis: "MANTIS_DISCORD_USER_ID",
},
botLoopProtection: {
// Allow up to five messages per minute before suppressing the pair.
maxEventsPerWindow: 5,
windowSeconds: 60,
cooldownSeconds: 90,
},
},
},
},

View File

@@ -185,6 +185,7 @@ Use these identifiers for delivery and allowlists:
audience: "https://gateway.example.com/googlechat",
webhookPath: "/googlechat",
botUser: "users/1234567890", // optional; helps mention detection
allowBots: false,
dm: {
policy: "pairing",
allowFrom: ["users/1234567890"],
@@ -216,6 +217,7 @@ Notes:
- Message actions expose `send` for text and `upload-file` for explicit attachment sends. `upload-file` accepts `media` / `filePath` / `path` plus optional `message`, `filename`, and thread targeting.
- `typingIndicator` supports `none`, `message` (default), and `reaction` (reaction requires user OAuth).
- Attachments are downloaded through the Chat API and stored in the media pipeline (size capped by `mediaMaxMb`).
- Bot-authored Google Chat messages are ignored by default. If you intentionally set `allowBots: true`, accepted bot-authored messages use shared [bot loop protection](/channels/bot-loop-protection). Configure `channels.defaults.botLoopProtection`, then override with `channels.googlechat.botLoopProtection` or `channels.googlechat.groups.<space>.botLoopProtection` when one space needs a different budget.
Secrets reference details: [Secrets Management](/gateway/secrets).

View File

@@ -18,6 +18,9 @@ Text is supported everywhere; media and reactions vary by channel.
- WhatsApp setup is install-on-demand: onboarding can show the setup flow before
the plugin package is installed, and the Gateway loads the external
ClawHub/npm plugin only when the channel is actually active.
- Channels that accept bot-authored inbound messages can use shared
[bot loop protection](/channels/bot-loop-protection) to prevent bot pairs from
replying to each other indefinitely.
## Supported channels

View File

@@ -266,6 +266,7 @@ Use `allowBots` when you intentionally want inter-agent Matrix traffic:
- `allowBots: true` accepts messages from other configured Matrix bot accounts in allowed rooms and DMs.
- `allowBots: "mentions"` accepts those messages only when they visibly mention this bot in rooms. DMs are still allowed.
- `groups.<room>.allowBots` overrides the account-level setting for one room.
- Accepted configured-bot messages use shared [bot loop protection](/channels/bot-loop-protection). Configure `channels.defaults.botLoopProtection`, then override with `channels.matrix.botLoopProtection` or `channels.matrix.groups.<room>.botLoopProtection` when one room needs a different budget.
- OpenClaw still ignores messages from the same Matrix user ID to avoid self-reply loops.
- Matrix does not expose a native bot flag here; OpenClaw treats "bot-authored" as "sent by another configured Matrix account on this OpenClaw gateway".

View File

@@ -920,6 +920,8 @@ Current Slack message actions include `send`, `upload-file`, `download-file`, `r
`allowBots` is conservative for channels and private channels: bot-authored room messages are accepted only when the sending bot is explicitly listed in that room's `users` allowlist, or when at least one explicit Slack owner ID from `channels.slack.allowFrom` is currently a room member. Wildcards and display-name owner entries do not satisfy owner presence. Owner presence uses Slack `conversations.members`; make sure the app has the matching read scope for the room type (`channels:read` for public channels, `groups:read` for private channels). If the member lookup fails, OpenClaw drops the bot-authored room message.
Accepted bot-authored Slack messages use shared [bot loop protection](/channels/bot-loop-protection). Configure `channels.defaults.botLoopProtection` for the default budget, then override with `channels.slack.botLoopProtection` or `channels.slack.channels.<id>.botLoopProtection` when a workspace or channel needs a different limit.
</Tab>
</Tabs>

View File

@@ -335,6 +335,7 @@ WhatsApp runs through the gateway's web channel (Baileys Web). It starts automat
- Use `user:<id>` (DM) or `channel:<id>` (guild channel) for delivery targets; bare numeric IDs are rejected.
- Guild slugs are lowercase with spaces replaced by `-`; channel keys use the slugged name (no `#`). Prefer guild IDs.
- Bot-authored messages are ignored by default. `allowBots: true` enables them; use `allowBots: "mentions"` to only accept bot messages that mention the bot (own messages still filtered).
- Channels that support bot-authored inbound messages can use shared [bot loop protection](/channels/bot-loop-protection). Set `channels.defaults.botLoopProtection` for baseline pair budgets, then override the channel or account only when one surface needs different limits.
- `channels.discord.guilds.<id>.ignoreOtherMentions` (and channel overrides) drops messages that mention another user or role but not the bot (excluding @everyone/@here).
- `channels.discord.mentionAliases` maps stable outbound `@handle` text to Discord user IDs before sending, so known teammates can be mentioned deterministically even when the transient directory cache is empty. Per-account overrides live under `channels.discord.accounts.<accountId>.mentionAliases`.
- `maxLinesPerMessage` (default 17) splits tall messages even when under 2000 chars.

View File

@@ -54,6 +54,46 @@ Internal OpenClaw runtime code has the same direction: load config once at the C
Provider and channel execution paths must use the active runtime config snapshot, not a file snapshot returned for config readback or editing. File snapshots preserve source values such as SecretRef markers for UI and writes; provider callbacks need the resolved runtime view. When a helper may be called with either the active source snapshot or the active runtime snapshot, route through `selectApplicableRuntimeConfig()` before reading credentials.
## Reusable runtime utilities
Use the channel-turn `botLoopProtection` facts for bot-authored inbound messages. Core applies the shared in-memory sliding-window guard before session record and dispatch, without tying the policy to one channel. The guard tracks `(scopeId, conversationId, participant pair)` keys, counts both directions of a pair together, applies a cooldown once the window budget is exceeded, and prunes inactive entries opportunistically.
Channel plugins that expose this behavior to operators should prefer the shared `channels.defaults.botLoopProtection` shape for baseline budgets, then layer channel/provider-specific overrides on top. The shared config uses seconds because it is user-facing:
```typescript
type ChannelBotLoopProtectionConfig = {
enabled?: boolean;
maxEventsPerWindow?: number;
windowSeconds?: number;
cooldownSeconds?: number;
};
```
Pass normalized bot-pair facts with the resolved turn. Core resolves defaults, unit conversion, and `enabled` semantics:
```typescript
return {
channel: "example",
routeSessionKey,
storePath,
ctxPayload,
recordInboundSession,
runDispatch,
botLoopProtection: {
scopeId: "account-1",
conversationId: "channel-1",
senderId: "bot-a",
receiverId: "bot-b",
config: channelConfig.botLoopProtection,
defaultsConfig: runtimeConfig.channels?.defaults?.botLoopProtection,
defaultEnabled: allowBotsMode !== "off",
},
};
```
Use `openclaw/plugin-sdk/pair-loop-guard-runtime` directly only for custom
two-party event loops that do not go through the shared channel-turn kernel.
## Runtime namespaces
<AccordionGroup>