Telegram's Bot-to-Bot Communication feature changes what a Telegram group can be. A group no longer has to be a place where humans talk and bots merely respond. It can become a visible multi-agent workspace where bots coordinate through the same messages humans can read.
This project, Telegram Bot-to-Bot Debate Club, is a working demonstration of that idea.
The demo has four Telegram bots:
-
DebaterRedBotargues for a proposition. -
DebaterBlueBotargues against it. -
SocraticBotquestions the weakest claim. -
JudgeBotdelivers a final verdict.
The important part is not that the bots debate. The important part is how they coordinate: every transition is a real Telegram group message delivered through Telegram's bot-to-bot mechanism. There is no hidden in-process bot registry, no direct method call between bots, and no local handoff fallback.
Built by Harish Kotra · Checkout my other builds
Why Bot-to-Bot Communication Matters
Most chatbots are designed around a human-to-bot model:
Human -> Bot -> Human
But many useful workflows are multi-role:
Human -> Intake Bot -> Research Bot -> Reviewer Bot -> Finalizer Bot
Without bot-to-bot delivery, developers often have to fake this with server-side orchestration. That works, but users cannot see the real control flow. The group chat becomes a UI facade over a hidden backend.
Telegram's Bot-to-Bot Communication enables a different model:
Bot A posts in the group.
Telegram delivers Bot A's message to Bot B.
Bot B decides whether to respond.
The whole workflow remains visible in the chat.
That visibility is valuable. It makes debugging easier, makes automation auditable, and lets humans understand why a bot responded.
The Showcase App
The debate club is intentionally small but realistic:
- A human starts with
/debate <topic>. - Red posts a FOR argument and mentions Blue.
- Blue receives Red's bot-authored message through Telegram and replies AGAINST.
- Blue mentions Socratic.
- Socratic receives Blue's bot-authored message through Telegram and challenges one debater.
- The targeted debater answers.
- After the configured exchange limit, a bot sends
/verdict@JudgeBot. - Judge receives that bot-authored command through Telegram and posts the verdict.
System Architecture
All four bots run in a single Node.js process, but each bot is a separate Telegram bot identity with its own token.
The single-process design keeps the showcase easy to run:
npm run dev
The important point is that shared process does not mean hidden coordination. The process shares state for safety, but bot-to-bot progression still depends on Telegram delivering bot-authored messages.
The Visible Control Plane
Red's job is to end its reply by tagging Blue:
@YourClubDebaterBlueBot what do you say?
Blue decides whether to respond by inspecting the Telegram message it received:
shouldRespond(msg: TelegramBot.Message): boolean {
if (this.isSelfMessage(msg)) {
return false;
}
return this.mentionsThisBot(msg);
}
That tiny check is the heart of the showcase. Blue is not called by Red. Blue is reacting to a Telegram update.
The same rule applies to the verdict:
/verdict@YourClubJudgeBot
Judge only responds to the visible Telegram command:
const isVerdictCommand =
text.startsWith(`/verdict@${CANONICAL_USERNAMES.judge.toLowerCase()}`) ||
text === '/verdict';
return hasDebateToJudge && isVerdictCommand;
The final trigger is not a hidden local state transition. It is a bot-to-bot Telegram command in the group.
Why Shared State Still Exists
Bot-to-Bot Communication gives you delivery. It does not give you workflow semantics.
In a group with four bots, every bot can receive many of the same group messages. Without turn control, multiple bots may answer at the wrong time or create loops.
The project uses a shared DebateState singleton:
export interface DebateState {
topic: string | null;
isActive: boolean;
isPaused: boolean;
roundNumber: number;
debaterRoundNumber: number;
totalMessageCount: number;
socraticDepth: number;
messages: DebateMessage[];
lastReplyAt: Record<string, number>;
seenMessageIds: Set<number>;
judgeHasFired: boolean;
verdictRequested: boolean;
debateId: number;
expectedResponder: BotRole | null;
pendingSocraticTarget: BotRole | null;
disqualified: Set<string>;
steerInstruction: string | null;
steerUntilRound: number | null;
}
The key field is expectedResponder.
if (
this.role !== 'judge' &&
this.state.expectedResponder &&
this.state.expectedResponder !== this.role
) {
return;
}
Telegram delivers messages. The app decides whose turn it is.
Counting The Right Thing
One subtle bug in multi-agent debates is counting every bot message as a round. That makes Socratic questions accidentally shorten the debate.
This project tracks two counters:
-
totalMessageCount: every bot debate message. -
debaterRoundNumber: completed Red/Blue exchanges.
The Judge is triggered after the intended debater exchange limit, not after Socratic questions.
The flow is:
Red -> Blue -> Socratic -> targeted debater -> other debater -> Socratic ...
When the max debater exchange limit is reached, Socratic can still ask one final question, and the targeted debater gets one final answer before the Judge command is sent.
That makes the demo feel like a coherent conversation instead of a timer.
Loop Guarding Bot-to-Bot Workflows
Bot-to-bot systems need guardrails. This project has a LoopGuard that handles:
- duplicate updates,
- per-bot cooldowns,
- pair-depth limits.
Deduplication is per receiving bot for normal bot-to-bot messages:
const seenKey = `${botUsername}:${messageId}`;
if (this.seenMessageKeys.has(seenKey)) return false;
this.seenMessageKeys.add(seenKey);
That detail matters. If dedupe were global, the first bot to see a group message could prevent the intended next bot from processing it.
Moderator commands use global dedupe because only one bot should handle /debate, /pause, /resume, and similar commands.
The LLM Layer
The LLM client is deliberately provider-neutral. It uses raw fetch against an OpenAI-compatible /v1/chat/completions endpoint:
const response = await fetch(this.completionsUrl, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
Authorization: `Bearer ${this.apiKey}`,
...this.extraHeaders,
},
body: JSON.stringify({ ...req, ...this.extraBody }),
});
That keeps the project compatible with:
- LM Studio,
- OpenRouter,
- Featherless.ai,
- local OpenAI-compatible servers,
- hosted inference gateways.
The expected response is simple:
const content = choice?.message?.content;
if (typeof content !== 'string' || content.trim().length === 0) {
throw new Error(this.describeEmptyContent(rawBody, choice, json.usage));
}
For LM Studio, a non-reasoning instruct model is best for Judge:
LLM_MODEL_JUDGE=mistralai/Mistral-7B-Instruct-v0.3
LLM_MAX_TOKENS=700
Reasoning models can spend their whole budget on reasoning_content and return no final answer. The app has a deterministic fallback verdict so the Telegram workflow still closes cleanly.
Typing Indicators
Multi-bot workflows need visible latency handling. If a bot is calling an LLM, users should know it is working.
The base class sends Telegram's typing action while work is in progress:
protected async withTypingIndicator<T>(work: () => Promise<T>): Promise<T> {
await this.sendTypingAction();
const interval = setInterval(() => {
void this.sendTypingAction();
}, 4000);
try {
return await work();
} finally {
clearInterval(interval);
}
}
This is especially important for bot-to-bot chains because users are watching a group conversation unfold.
BotFather And Group Setup
The project needs four bot tokens:
DEBATER_RED_TOKEN=
DEBATER_BLUE_TOKEN=
SOCRATIC_TOKEN=
JUDGE_TOKEN=
It also needs exact usernames:
DEBATER_RED_USERNAME=YourClubDebaterRedBot
DEBATER_BLUE_USERNAME=YourClubDebaterBlueBot
SOCRATIC_USERNAME=YourClubSocraticBot
JUDGE_USERNAME=YourClubJudgeBot
The most important setup checklist:
- Enable Bot-to-Bot Communication Mode for all four bots in BotFather if available.
- Add all four bots to the same Telegram group.
- Give Socratic admin rights, or disable Group Privacy Mode.
- Confirm bot-authored messages appear in routing logs.
When DEBUG_BOT_ROUTING=true, a healthy run shows lines like:
[YourClubDebaterBlueBot] received: message_id=288 from=@YourClubDebaterRedBot ...
[YourClubSocraticBot] received: message_id=289 from=@YourClubDebaterBlueBot ...
[YourClubJudgeBot] received: message_id=307 from=@YourClubDebaterBlueBot text="/verdict@YourClubJudgeBot"
That is the proof that Telegram is doing the bot-to-bot delivery.
What Developers Can Build From This
The same pattern applies far beyond debate bots.
You can fork this into:
- an incident-response room where triage, diagnosis, and comms bots collaborate,
- a customer-support workflow where intake, policy, and escalation bots hand off visibly,
- a code-review panel with architecture, security, and testing bots,
- a classroom simulation where different characters challenge a student's answer,
- an AI game where specialized bots play roles in the group,
- a research workflow where agents critique each other's sources.
The reusable pieces are:
- one Telegram bot identity per role,
- visible messages as routing events,
- exact username mentions,
- explicit bot commands like
/verdict@TargetBot, - shared state for turn control,
- loop guards for safety,
- routing logs for setup diagnosis.
Production Notes
This repo is local-first and intentionally simple. For production, consider:
- persistent state with SQLite or Postgres,
- structured logs,
- health checks,
- process supervision,
- per-group state if running in multiple Telegram groups,
- webhook mode if deploying to a server,
- distributed locks if running multiple replicas.
Keep one rule intact if your goal is to showcase Bot-to-Bot Communication: do not add invisible local handoffs between bots. Let Telegram deliver the bot-authored messages, and use your application state only to decide whether a bot should answer.
Telegram Bot-to-Bot Communication turns the group chat into an orchestration surface. This project demonstrates that with a debate club, but the underlying pattern is broader: visible, auditable, role-based multi-agent workflows inside Telegram.
That is the piece worth building on.
Docs: https://core.telegram.org/bots/features#bot-to-bot-communication
Code and more: https://www.dailybuild.xyz/project/138-telegram-debate-club


Top comments (0)