Install
$ agentstack add skill-sskarz-nanoclawbster-add-telegram-swarm ✓ scanned · ✓ verified, works with Claude Code, Cursor, and more.
Security review
✓ PassedNo issues found. Passed automated security review. · v0.1.0 How review works →
- ✓ Prompt-injection patterns
- ✓ Secret / credential exfiltration
- ✓ Dangerous shell & filesystem operations
- ✓ Untrusted network calls
- ✓ Known-malicious package signatures
What it can access
- ● Network access Used
- ✓ Filesystem access No
- ✓ Shell / process execution No
- ● Environment & secrets Used
- ✓ Dynamic code execution No
From automated source analysis of v0.1.0. “Used” means the capability is present in the source — more access means more to trust, not that it’s unsafe.
Verified badge
Passed review? Show it. Paste this badge into your README, it links to the public security report.
Reliability & compatibility
Declared compatibility
Compatibility is declared by the source manifest. End-to-end runtime verification is coming, see below.
We're building live execution health for every listing: tool-call success rate, median latency, uptime, and last-checked timestamps, measured, not self-reported. It isn't live yet, so we don't show numbers we can't stand behind.
How agent discovery & health will work →About
Add Agent Swarm to Telegram
This skill adds Agent Teams (Swarm) support to an existing Telegram channel. Each subagent in a team gets its own bot identity in the Telegram group, so users can visually distinguish which agent is speaking.
Prerequisite: Telegram must already be set up via the /add-telegram skill. If src/telegram.ts does not exist or TELEGRAM_BOT_TOKEN is not configured, tell the user to run /add-telegram first.
How It Works
- The main bot receives messages and sends lead agent responses (already set up by
/add-telegram) - Pool bots are send-only — each gets a Grammy
Apiinstance (no polling) - When a subagent calls
send_messagewith asenderparameter, the host assigns a pool bot and renames it to match the sender's role - Messages appear in Telegram from different bot identities
Subagent calls send_message(text: "Found 3 results", sender: "Researcher")
→ MCP writes IPC file with sender field
→ Host IPC watcher picks it up
→ Assigns pool bot #2 to "Researcher" (round-robin, stable per-group)
→ Renames pool bot #2 to "Researcher" via setMyName
→ Sends message via pool bot #2's Api instance
→ Appears in Telegram from "Researcher" bot
Prerequisites
1. Create Pool Bots
Tell the user:
> I need you to create 3-5 Telegram bots to use as the agent pool. These will be renamed dynamically to match agent roles. > > 1. Open Telegram and search for @BotFather > 2. Send /newbot for each bot: > - Give them any placeholder name (e.g., "Bot 1", "Bot 2") > - Usernames like myproject_swarm_1_bot, myproject_swarm_2_bot, etc. > 3. Copy all the tokens > 4. Add all bots to your Telegram group(s) where you want agent teams
Wait for user to provide the tokens.
2. Disable Group Privacy for Pool Bots
Tell the user:
> Important: Each pool bot needs Group Privacy disabled so it can send messages in groups. > > For each pool bot in @BotFather: > 1. Send /mybots and select the bot > 2. Go to Bot Settings > Group Privacy > Turn off > > Then add all pool bots to your Telegram group(s).
Implementation
Step 1: Update Configuration
Read src/config.ts and add the bot pool config near the other Telegram exports:
export const TELEGRAM_BOT_POOL = (process.env.TELEGRAM_BOT_POOL || '')
.split(',')
.map((t) => t.trim())
.filter(Boolean);
Step 2: Add Bot Pool to Telegram Module
Read src/telegram.ts and add the following:
- Update imports — add
Apito the Grammy import:
import { Api, Bot } from 'grammy';
- Add pool state after the existing
let botdeclaration:
// Bot pool for agent teams: send-only Api instances (no polling)
const poolApis: Api[] = [];
// Maps "{groupFolder}:{senderName}" → pool Api index for stable assignment
const senderBotMap = new Map();
let nextPoolIndex = 0;
- Add pool functions — place these before the
isTelegramConnectedfunction:
/**
* Initialize send-only Api instances for the bot pool.
* Each pool bot can send messages but doesn't poll for updates.
*/
export async function initBotPool(tokens: string[]): Promise {
for (const token of tokens) {
try {
const api = new Api(token);
const me = await api.getMe();
poolApis.push(api);
logger.info(
{ username: me.username, id: me.id, poolSize: poolApis.length },
'Pool bot initialized',
);
} catch (err) {
logger.error({ err }, 'Failed to initialize pool bot');
}
}
if (poolApis.length > 0) {
logger.info({ count: poolApis.length }, 'Telegram bot pool ready');
}
}
/**
* Send a message via a pool bot assigned to the given sender name.
* Assigns bots round-robin on first use; subsequent messages from the
* same sender in the same group always use the same bot.
* On first assignment, renames the bot to match the sender's role.
*/
export async function sendPoolMessage(
chatId: string,
text: string,
sender: string,
groupFolder: string,
): Promise {
if (poolApis.length === 0) {
// No pool bots — fall back to main bot
await sendTelegramMessage(chatId, text);
return;
}
const key = `${groupFolder}:${sender}`;
let idx = senderBotMap.get(key);
if (idx === undefined) {
idx = nextPoolIndex % poolApis.length;
nextPoolIndex++;
senderBotMap.set(key, idx);
// Rename the bot to match the sender's role, then wait for Telegram to propagate
try {
await poolApis[idx].setMyName(sender);
await new Promise((r) => setTimeout(r, 2000));
logger.info({ sender, groupFolder, poolIndex: idx }, 'Assigned and renamed pool bot');
} catch (err) {
logger.warn({ sender, err }, 'Failed to rename pool bot (sending anyway)');
}
}
const api = poolApis[idx];
try {
const numericId = chatId.replace(/^tg:/, '');
const MAX_LENGTH = 4096;
if (text.length {
const data: Record = {
type: 'message',
chatJid,
text: args.text,
sender: args.sender || undefined,
groupFolder,
timestamp: new Date().toISOString(),
};
writeIpcFile(MESSAGES_DIR, data);
return { content: [{ type: 'text' as const, text: 'Message sent.' }] };
},
Step 4: Update Host IPC Routing
Read src/ipc.ts and make these changes:
- Add imports — add
sendPoolMessageandinitBotPoolfrom the Telegram swarm module, andTELEGRAM_BOT_POOLfrom config.
- Update IPC message routing — in
src/ipc.ts, find where thesendMessagedependency is called to deliver IPC messages (insideprocessIpcFiles). ThesendMessageis passed in via theIpcDepsparameter. Wrap it to route Telegram swarm messages through the bot pool:
if (data.sender && data.chatJid.startsWith('tg:')) {
await sendPoolMessage(
data.chatJid,
data.text,
data.sender,
sourceGroup,
);
} else {
await deps.sendMessage(data.chatJid, data.text);
}
Note: The assistant name prefix is handled by formatOutbound() in the router — Telegram channels have prefixAssistantName = false so no prefix is added for tg: JIDs.
- Initialize pool in
main()insrc/index.ts— after creating the Telegram channel, add:
if (TELEGRAM_BOT_POOL.length > 0) {
await initBotPool(TELEGRAM_BOT_POOL);
}
Step 5: Update CLAUDE.md Files
5a. Add global message formatting rules
Read groups/global/CLAUDE.md and add a Message Formatting section:
## Message Formatting
NEVER use markdown. Only use WhatsApp/Telegram formatting:
- *single asterisks* for bold (NEVER **double asterisks**)
- _underscores_ for italic
- • bullet points
- ```triple backticks``` for code
No ## headings. No [links](url). No **double stars**.
5b. Update existing group CLAUDE.md headings
In any group CLAUDE.md that has a "WhatsApp Formatting" section (e.g. groups/main/CLAUDE.md), rename the heading to reflect multi-channel support:
## WhatsApp Formatting (and other messaging apps)
5c. Add Agent Teams instructions to Telegram groups
For each Telegram group that will use agent teams, create or update its groups/{folder}/CLAUDE.md with these instructions. Read the existing CLAUDE.md first (or groups/global/CLAUDE.md as a base) and add the Agent Teams section:
## Agent Teams
When creating a team to tackle a complex task, follow these rules:
### CRITICAL: Follow the user's prompt exactly
Create *exactly* the team the user asked for — same number of agents, same roles, same names. Do NOT add extra agents, rename roles, or use generic names like "Researcher 1". If the user says "a marine biologist, a physicist, and Alexander Hamilton", create exactly those three agents with those exact names.
### Team member instructions
Each team member MUST be instructed to:
1. *Share progress in the group* via `mcp__nanoclawbster__send_message` with a `sender` parameter matching their exact role/character name (e.g., `sender: "Marine Biologist"` or `sender: "Alexander Hamilton"`). This makes their messages appear from a dedicated bot in the Telegram group.
2. *Also communicate with teammates* via `SendMessage` as normal for coordination.
3. Keep group messages *short* — 2-4 sentences max per message. Break longer content into multiple `send_message` calls. No walls of text.
4. Use the `sender` parameter consistently — always the same name so the bot identity stays stable.
5. NEVER use markdown formatting. Use ONLY WhatsApp/Telegram formatting: single *asterisks* for bold (NOT **double**), _underscores_ for italic, • for bullets, ```backticks``` for code. No ## headings, no [links](url), no **double asterisks**.
### Example team creation prompt
When creating a teammate, include instructions like:
\```
You are the Marine Biologist. When you have findings or updates for the user, send them to the group using mcp__nanoclawbster__send_message with sender set to "Marine Biologist". Keep each message short (2-4 sentences max). Use emojis for strong reactions. ONLY use single *asterisks* for bold (never **double**), _underscores_ for italic, • for bullets. No markdown. Also communicate with teammates via SendMessage.
\```
### Lead agent behavior
As the lead agent who created the team:
- You do NOT need to react to or relay every teammate message. The user sees those directly from the teammate bots.
- Send your own messages only to comment, share thoughts, synthesize, or direct the team.
- When processing an internal update from a teammate that doesn't need a user-facing response, wrap your *entire* output in `` tags.
- Focus on high-level coordination and the final synthesis.
Step 6: Update Environment
Add pool tokens to .env:
TELEGRAM_BOT_POOL=TOKEN1,TOKEN2,TOKEN3,...
Important: Sync to all required locations:
cp .env data/env/env
Also add TELEGRAM_BOT_POOL to the launchd plist (~/Library/LaunchAgents/com.nanoclawbster.plist) in the EnvironmentVariables dict if using launchd.
Step 7: Rebuild and Restart
npm run build
./container/build.sh # Required — MCP tool changed
# macOS:
launchctl unload ~/Library/LaunchAgents/com.nanoclawbster.plist
launchctl load ~/Library/LaunchAgents/com.nanoclawbster.plist
# Linux:
# systemctl --user restart nanoclawbster
Must use unload/load (macOS) or restart (Linux) because the service env vars changed.
Step 8: Test
Tell the user:
> Send a message in your Telegram group asking for a multi-agent task, e.g.: > "Assemble a team of a researcher and a coder to build me a hello world app" > > You should see: > - The lead agent (main bot) acknowledging and creating the team > - Each subagent messaging from a different bot, renamed to their role > - Short, scannable messages from each agent > > Check logs: tail -f logs/nanoclawbster.log | grep -i pool
Architecture Notes
- Pool bots use Grammy's
Apiclass — lightweight, no polling, just send - Bot names are set via
setMyName— changes are global to the bot, not per-chat - A 2-second delay after
setMyNameallows Telegram to propagate the name change before the first message - Sender→bot mapping is stable within a group (keyed as
{groupFolder}:{senderName}) - Mapping resets on service restart — pool bots get reassigned fresh
- If pool runs out, bots are reused (round-robin wraps)
Troubleshooting
Pool bots not sending messages
- Verify tokens:
curl -s "https://api.telegram.org/botTOKEN/getMe" - Check pool initialized:
grep "Pool bot" logs/nanoclawbster.log - Ensure all pool bots are members of the Telegram group
- Check Group Privacy is disabled for each pool bot
Bot names not updating
Telegram caches bot names client-side. The 2-second delay after setMyName helps, but users may need to restart their Telegram client to see updated names immediately.
Subagents not using send_message
Check the group's CLAUDE.md has the Agent Teams instructions. The lead agent reads this when creating teammates and must include the send_message + sender instructions in each teammate's prompt.
Removal
To remove Agent Swarm support while keeping basic Telegram:
- Remove
TELEGRAM_BOT_POOLfromsrc/config.ts - Remove pool code from
src/telegram.ts(poolApis,senderBotMap,initBotPool,sendPoolMessage) - Remove pool routing from IPC handler in
src/index.ts(revert to plainsendMessage) - Remove
initBotPoolcall frommain() - Remove
senderparam from MCP tool incontainer/agent-runner/src/ipc-mcp-stdio.ts - Remove Agent Teams section from group CLAUDE.md files
- Remove
TELEGRAM_BOT_POOLfrom.env,data/env/env, and launchd plist/systemd unit - Rebuild:
npm run build && ./container/build.sh && launchctl unload ~/Library/LaunchAgents/com.nanoclawbster.plist && launchctl load ~/Library/LaunchAgents/com.nanoclawbster.plist(macOS) ornpm run build && ./container/build.sh && systemctl --user restart nanoclawbster(Linux)
Source & license
This open-source skill is cataloged on AgentStack and links to its original source — we do not rehost the code.
- Author: sskarz
- Source: sskarz/nanoclawbster
- License: MIT
- Homepage: https://nanoclaw.dev
Install and usage instructions live in the source repository linked above.
Reviews
No reviews yet, be the first.
Write a review
Versions
- v0.1.0 Imported from the upstream source.