Loading...
Loading...
Add Agent Swarm (Teams) support to Telegram. Each subagent gets its own bot identity in the group. Requires Telegram channel to be set up first (use /add-telegram). Triggers on "agent swarm", "agent teams telegram", "telegram swarm", "bot pool".
npx skill4agent add 0-cyberdyne-systems-0/fft_nano add-telegram-swarm/add-telegramsrc/telegram.tsTELEGRAM_BOT_TOKEN/add-telegram/add-telegramApisend_messagesenderSubagent 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" botI need you to create 3-5 Telegram bots to use as the agent pool. These will be renamed dynamically to match agent roles.
- Open Telegram and search for
@BotFather- Send
for each bot:/newbot
- Give them any placeholder name (e.g., "Bot 1", "Bot 2")
- Usernames like
,myproject_swarm_1_bot, etc.myproject_swarm_2_bot- Copy all the tokens
- Add all bots to your Telegram group(s) where you want agent teams
Important: Each pool bot needs Group Privacy disabled so it can send messages in groups.For each pool bot in:@BotFather
- Send
and select the bot/mybots- Go to Bot Settings > Group Privacy > Turn off
Then add all pool bots to your Telegram group(s).
src/config.tsexport const TELEGRAM_BOT_POOL = (process.env.TELEGRAM_BOT_POOL || '')
.split(',')
.map((t) => t.trim())
.filter(Boolean);src/telegram.tsApiimport { Api, Bot } from 'grammy';let bot// 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<string, number>();
let nextPoolIndex = 0;isTelegramConnected/**
* 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<void> {
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<void> {
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 <= MAX_LENGTH) {
await api.sendMessage(numericId, text);
} else {
for (let i = 0; i < text.length; i += MAX_LENGTH) {
await api.sendMessage(numericId, text.slice(i, i + MAX_LENGTH));
}
}
logger.info({ chatId, sender, poolIndex: idx, length: text.length }, 'Pool message sent');
} catch (err) {
logger.error({ chatId, sender, err }, 'Failed to send pool message');
}
}container/agent-runner/src/ipc-mcp-stdio.tssend_messagesender{ text: z.string().describe('The message text to send') },{
text: z.string().describe('The message text to send'),
sender: z.string().optional().describe('Your role/identity name (e.g. "Researcher"). When set, messages appear from a dedicated bot in Telegram.'),
},senderasync (args) => {
const data: Record<string, string | undefined> = {
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.' }] };
},src/index.tssendPoolMessageinitBotPoolTELEGRAM_BOT_POOLstartIpcWatcherprocessIpcFilesawait sendMessage(
data.chatJid,
`${ASSISTANT_NAME}: ${data.text}`,
);if (data.sender && data.chatJid.startsWith('tg:')) {
await sendPoolMessage(
data.chatJid,
data.text,
data.sender,
sourceGroup,
);
} else {
// Telegram bots already show their name — skip prefix for tg: chats
const prefix = data.chatJid.startsWith('tg:') ? '' : `${ASSISTANT_NAME}: `;
await sendMessage(
data.chatJid,
`${prefix}${data.text}`,
);
}main()connectTelegram()if (TELEGRAM_BOT_POOL.length > 0) {
await initBotPool(TELEGRAM_BOT_POOL);
}groups/global/CLAUDE.md## 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**.groups/main/CLAUDE.md## WhatsApp Formatting (and other messaging apps)groups/{folder}/CLAUDE.mdgroups/global/CLAUDE.md## 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__nanoclaw__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__nanoclaw__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 `<internal>` tags.
- Focus on high-level coordination and the final synthesis..envTELEGRAM_BOT_POOL=TOKEN1,TOKEN2,TOKEN3,...cp .env data/env/envTELEGRAM_BOT_POOL~/Library/LaunchAgents/com.nanoclaw.plistEnvironmentVariablesnpm run build
./container/build.sh # Required — MCP tool changed
launchctl unload ~/Library/LaunchAgents/com.nanoclaw.plist
launchctl load ~/Library/LaunchAgents/com.nanoclaw.plistunload/loadkickstartSend 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/nanoclaw.log | grep -i pool
ApisetMyNamesetMyName{groupFolder}:{senderName}curl -s "https://api.telegram.org/botTOKEN/getMe"grep "Pool bot" logs/nanoclaw.logsetMyNameCLAUDE.mdsend_messagesenderTELEGRAM_BOT_POOLsrc/config.tssrc/telegram.tspoolApissenderBotMapinitBotPoolsendPoolMessagesrc/index.tssendMessageinitBotPoolmain()sendercontainer/agent-runner/src/ipc-mcp-stdio.tsTELEGRAM_BOT_POOL.envdata/env/envnpm run build && ./container/build.sh && launchctl unload ~/Library/LaunchAgents/com.nanoclaw.plist && launchctl load ~/Library/LaunchAgents/com.nanoclaw.plist