Skip to main content
BotLoopGuard breaks runaway bot-to-bot reply loops — two bots answering each other forever — by budgeting how many exchanges a pair may have inside a sliding window. Once a channel opts in with allow_bots: true, the guard is auto-instantiated per adapter — no code to wire.
That’s it — the BotLoopGuard is auto-instantiated per adapter, and any A↔B pair that exceeds 20 exchanges in a 60s window is suppressed for a 60s cooldown. Two bots ping-pong replies; the guard counts each exchange and, once a pair passes its budget, drops the turn and opens a cooldown.
BotLoopGuard is a gateway-layer primitive: it decides whether to admit one bot’s reply to another bot. It differs from Loop Guard, which caps tool calls inside a single agent turn.

Quick Start

1

Enable with allow_bots: true

Flip the opt-in on the channel — everything else uses the default budget (20 exchanges / 60s window / 60s cooldown).
2

Tune the budget

Add bot_loop_protection keys to change how quickly a loop trips and how long it stays quiet.
The same two fields exist on BotConfig for the Python constructor path:
3

Advanced — wire the primitive yourself

Custom adapters that do not inherit MessageHookMixin can call BotLoopGuard.observe(...) directly — this is the escape hatch, not the recommended path.

How It Works

The guard tracks each bot pair in a sliding window; exchange #21 trips the budget and both bots go quiet for the cooldown. A → B and B → A collapse to the same pair key, so a ping-pong loop accrues one budget instead of two independent streams.

How adapters wire it

Every adapter inherits MessageHookMixin.bot_loop_allows(sender, self_bot_id=...), the shared helper that folds the allow_bots opt-in and the per-pair budget into one verdict — a human sender always passes, a bot sender passes only when allow_bots: true and the pair is under budget.

When to Enable

BotLoopGuard only matters when your gateway accepts bot-authored inbound messages and multiple bots can share a room.

Configuration Options

Two BotConfig fields turn the guard on and shape its budget. Both match praisonaiagents/bots/config.py. bot_loop_protection accepts the same keys as BotLoopPolicy, coerced via BotLoopPolicy.from_dict(...). Every field matches praisonaiagents/bots/silence.py. BotLoopPolicy.from_dict(data) builds a policy from a mapping; None returns the enabled default and unknown keys are ignored. BotLoopPolicy.from_dict tolerates malformed numeric values — a typo like max_events_per_window: "abc" falls back to the field default instead of crashing the inbound path on the first bot message. The runtime BotLoopGuard exposes this surface:
BotLoopGuard ships in the Python SDK only. TypeScript and Rust ports do not exist yet — do not wire it into docs/js/ or docs/rust/.

Common Patterns

Once allow_bots: true is set the guard runs automatically; these primitive-level patterns cover custom adapters and tests. Group room with multiple assistants — use one guard per gateway and call observe() only when the sender is a bot.
Testing with an injected clock — pass explicit now= values for deterministic time-dependent tests.
Resetting on gateway restart — call reset() after redeploy so leftover cooldowns from an earlier process don’t leak.

When It Fires vs Doesn’t

Only bot-authored inbound turns reach the guard; humans and single-bot deployments never call observe().

Best Practices

Skip observe() when the sender is human — the guard is zero-cost on the common path.
Use a shorter window_seconds with a higher cooldown_seconds for chatty rooms — the window sizes urgency, the cooldown sizes recovery.
max_events_per_window=0 is clamped to 1 and hides intent. Set enabled=False to turn the guard off.
The deque/dict state is per-instance and per-pair, so a single shared BotLoopGuard tracks the whole gateway.
Pair the guard with allow_silence and Intentional Silence for full ambient-channel safety — the agent chooses to stay quiet, and the guard stops runaway loops.

Intentional Silence

The sibling NO_REPLY primitive in the same silence.py file.

Messaging Bots

Where bot inbound flow originates.

Loop Guard

Per-turn tool-call guard — a different layer inside one agent.

Gateway

Channel configuration where the guard sits.

Bot Gateway → Channel Security

The allow_bots / bot_loop_protection YAML surface on gateway.yaml.