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.
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 defaults

Call observe() for each bot-authored inbound turn and drop the turn when it returns False.
2

Tune the budget with BotLoopPolicy

Pass a BotLoopPolicy to change how quickly a loop trips and how long it stays quiet.
3

Build the policy from YAML/kwargs

BotLoopPolicy.from_dict builds a policy from a config mapping and ignores unknown keys.

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.

When to Enable

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

Configuration Options

BotLoopPolicy declares the per-pair budget. 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. 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

Three patterns cover most gateway deployments. 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.