Two Bots in One Channel — Stopping the Loop Before It Costs You

·5 min read·Updated
discordtelegrambotsllmreliability

If your bot replies to messages from other bots, two bots in one channel can talk to each other forever. Telegram removed the possibility at the platform level and says exactly why: "Bots talking to each other could potentially get stuck in unwelcome loops. To avoid this, we decided that bots will not be able to see messages from other bots regardless of mode" (Telegram FAQ). Discord makes no such guarantee, so the check is yours to write — and if a language model is generating the replies, every lap of that loop is a paid API call, an API request counting toward a stricter rate limit, and a message somebody has to read.

The three checks

On Discord, ignore messages that aren't from a person, before doing any work:

if message.author.bot:
    return
if message.author.id == bot.user.id:   # your own messages
    return
if message.webhook_id is not None:      # webhook posts
    return

The self-check matters more than it looks. The most common accidental loop isn't two bots — it's one bot reacting to its own output. A bot that replies to any message containing a keyword, and whose reply contains that keyword, will happily converse with itself.

Webhooks are a separate case. A webhook message isn't from an application in the usual sense, so treating "is it a bot" as one boolean can let them through. If your bot posts via a webhook and also reacts to webhook messages, that's a loop with only one bot involved.

Put these checks first in the handler, before any filtering, database read, or model call — the point is to spend nothing on messages you'll discard.

Why it's worse with a model behind it

A rules-based loop is embarrassing and cheap. A model-backed loop is expensive in three directions at once:

  • Every lap is billed. Two chatty LLM bots can spend real money in the time it takes you to notice, and each reply's context is longer than the last if you're keeping conversation history.
  • Every lap is an API request. Sustained traffic runs into rate limits, and each 429 counts toward the invalid-request limit that restricts your IP.
  • Latency doesn't save you. People assume a slow model call breaks the cycle. It doesn't — it just makes the loop slower and harder to notice, while the meter keeps running.

This is why the spend limit at your provider matters more than any in-app guard: it's the one control that still holds when the guard has a bug.

When you actually want bot-to-bot

Sometimes it's the feature — a bot that reacts to a CI notification, or a small pipeline of bots. Then don't relax the check, replace it with something narrower:

  • Allowlist specific IDs. "Ignore all bots except these two" is a much smaller surface than "respond to bots".
  • Carry a hop count. Include a marker in the message — a counter, or an invisible tag — and refuse to act past a small depth. That converts an infinite loop into a bounded one.
  • Make one direction terminal. Design the pipeline so at least one participant never replies to a reply. A cycle needs every node to keep going.

Loop breakers worth having anyway

Even with correct checks, defensive limits are cheap and pay for themselves the first time something surprising happens:

  • A per-channel cooldown. Don't reply to the same channel more than once every few seconds. Users won't notice; runaway loops will.
  • A replies-per-minute cap. Above the cap, stop responding and log it. Keep the counter on a volume rather than in memory — an in-memory counter resets on every deploy, which is precisely when you're most likely to be shipping the bug.
  • A kill switch. An environment variable your handler checks — BOT_ENABLED=false — turns the bot off with a restart instead of a redeploy or a delete. It's the fastest recovery you can give yourself at 2am.

How to notice quickly

A loop shows as a distinctive log signature: the same handler firing repeatedly, seconds apart, with the same channel ID. That's easy to spot if you log one line per handled message with the channel and author.

It's also a good argument for logging token usage per request — a sudden step change in tokens per minute is a loop before it's an invoice.

And if you have alerting, "more than N replies in a minute" is a better trigger than anything about errors, because a loop is perfectly healthy from the app's point of view. Nothing is watching for you.

What Telegram's choice tells you

Telegram's rule is absolute — bots can't see other bots' messages "regardless of mode", whether privacy mode is on or off, and regardless of admin status.

That's worth noticing as a design signal rather than just a platform difference. The people who run a very large bot ecosystem decided this class of bug was common enough to remove the possibility entirely. If you're on Discord, you're getting the flexibility and the responsibility together.

FAQ

Can Telegram bots reply to each other?

No. Bots can't see messages from other bots regardless of privacy mode, specifically to prevent loops.

How do I stop my Discord bot replying to other bots?

Check author.bot and return early. Also check for your own ID and for webhook messages, which are a separate case.

Why does my bot reply to itself?

Its own output matches whatever condition triggers it. Compare the author ID to your bot's ID before doing anything else.

What if I want two of my bots to talk?

Allowlist the specific bot IDs rather than allowing bots generally, and carry a hop counter so any cycle terminates.

How expensive can a loop get?

With a model generating replies, every exchange is billed and the context often grows each turn. Set a spend limit at your provider as the backstop.

How do I recover quickly?

A kill-switch environment variable your handler checks lets you stop the behaviour with a restart. Stopping the app entirely also works and is faster than debugging.

Wrapping up

This is a failure with an unusual property: everything is working. No errors, no crashes, no red status — just two programs being polite to each other several times a minute at your expense. Put the author checks at the top of the handler where they cost nothing, treat webhooks and your own messages as separate cases, and keep a cap and a kill switch for the day something surprises you. Telegram thought this was worth designing out entirely, which is a reasonable hint about how often it happens.