[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"skill:01M6H3S2RHCSYA0FHPR8GEEJ1B":3,"skill-same:01M6H3S2RHCSYA0FHPR8GEEJ1B":70},{"skillNo":4,"name":5,"nameZh":6,"description":7,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":16,"categories":17,"keywords":23,"publishedAt":44,"linkOnly":45,"markdownContent":46,"markdownBody":47,"frontMatter":48,"refName":50,"commitSha":51,"skillDirectory":52,"forksCount":53,"repositoryHtmlUrl":54,"repositoryHomepageUrl":54,"ownerHtmlUrl":55,"ownerBlogUrl":6,"ownerDescription":6,"updatedAt":56,"install":57,"resources":69},"01M6H3S2RHCSYA0FHPR8GEEJ1B","add-telegram-swarm",null,"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 \u002Fadd-telegram). Triggers on \"agent swarm\", \"agent teams telegram\", \"telegram swarm\", \"bot pool\".","sliamh11\u002FDeus","sliamh11","https:\u002F\u002Favatars.githubusercontent.com\u002Fu\u002F68326628?v=4","01M6H3PPFXQ6C98QV1RJA5W72S","Liam Steiner","01M6H3PPM81GJC6KYCNTY9A3AT",51,"MIT","https:\u002F\u002Fgithub.com\u002Fsliamh11\u002FDeus\u002Fblob\u002Fmain\u002F.claude\u002Fskills\u002Fadd-telegram-swarm\u002FSKILL.md",[18],{"categoryNo":19,"categoryName":20,"taxonomyType":21,"taxonomyTypeDesc":22},"01JBSEEDCAT000000000000023","Skill与智能体开发","SCENARIO","使用场景",[24,25,26,27,28,29,30,31,32,33,34,35,36,37,38,39,40,41,42,43],"add telegram","add","add telegram swarm","swarm","telegram","channel","agent swarm","true","triggers","agent teams","up","use","bot","add agent","subagent","first","SET","teams","identity","support","2026-11-10 10:12:30",false,"---\nname: add-telegram-swarm\ndescription: 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 \u002Fadd-telegram). Triggers on \"agent swarm\", \"agent teams telegram\", \"telegram swarm\", \"bot pool\".\ndisable-model-invocation: true\n---\n\n# Add Agent Swarm to Telegram\n\nThis 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.\n\n**Prerequisite**: Telegram must already be set up via the `\u002Fadd-telegram` skill. If `src\u002Ftelegram.ts` does not exist or `TELEGRAM_BOT_TOKEN` is not configured, tell the user to run `\u002Fadd-telegram` first.\n\n## How It Works\n\n- The **main bot** receives messages and sends lead agent responses (already set up by `\u002Fadd-telegram`)\n- **Pool bots** are send-only — each gets a Grammy `Api` instance (no polling)\n- When a subagent calls `send_message` with a `sender` parameter, the host assigns a pool bot and renames it to match the sender's role\n- Messages appear in Telegram from different bot identities\n\n```\nSubagent calls send_message(text: \"Found 3 results\", sender: \"Researcher\")\n  → MCP writes IPC file with sender field\n  → Host IPC watcher picks it up\n  → Assigns pool bot #2 to \"Researcher\" (round-robin, stable per-group)\n  → Renames pool bot #2 to \"Researcher\" via setMyName\n  → Sends message via pool bot #2's Api instance\n  → Appears in Telegram from \"Researcher\" bot\n```\n\n## Prerequisites\n\n### 1. Create Pool Bots\n\nTell the user:\n\n> I need you to create 3-5 Telegram bots to use as the agent pool. These will be renamed dynamically to match agent roles.\n>\n> 1. Open Telegram and search for `@BotFather`\n> 2. Send `\u002Fnewbot` for each bot:\n>    - Give them any placeholder name (e.g., \"Bot 1\", \"Bot 2\")\n>    - Usernames like `myproject_swarm_1_bot`, `myproject_swarm_2_bot`, etc.\n> 3. Copy all the tokens\n> 4. Add all bots to your Telegram group(s) where you want agent teams\n\nWait for user to provide the tokens.\n\n### 2. Disable Group Privacy for Pool Bots\n\nTell the user:\n\n> **Important**: Each pool bot needs Group Privacy disabled so it can send messages in groups.\n>\n> For each pool bot in `@BotFather`:\n> 1. Send `\u002Fmybots` and select the bot\n> 2. Go to **Bot Settings** > **Group Privacy** > **Turn off**\n>\n> Then add all pool bots to your Telegram group(s).\n\n## Implementation\n\n### Step 1: Update Configuration\n\nRead `src\u002Fconfig.ts` and add the bot pool config near the other Telegram exports:\n\n```typescript\nexport const TELEGRAM_BOT_POOL = (process.env.TELEGRAM_BOT_POOL || '')\n  .split(',')\n  .map((t) => t.trim())\n  .filter(Boolean);\n```\n\n### Step 2: Add Bot Pool to Telegram Module\n\nRead `src\u002Ftelegram.ts` and add the following:\n\n1. **Update imports** — add `Api` to the Grammy import:\n\n```typescript\nimport { Api, Bot } from 'grammy';\n```\n\n2. **Add pool state** after the existing `let bot` declaration:\n\n```typescript\n\u002F\u002F Bot pool for agent teams: send-only Api instances (no polling)\nconst poolApis: Api[] = [];\n\u002F\u002F Maps \"{groupFolder}:{senderName}\" → pool Api index for stable assignment\nconst senderBotMap = new Map\u003Cstring, number>();\nlet nextPoolIndex = 0;\n```\n\n3. **Add pool functions** — place these before the `isTelegramConnected` function:\n\n```typescript\n\u002F**\n * Initialize send-only Api instances for the bot pool.\n * Each pool bot can send messages but doesn't poll for updates.\n *\u002F\nexport async function initBotPool(tokens: string[]): Promise\u003Cvoid> {\n  for (const token of tokens) {\n    try {\n      const api = new Api(token);\n      const me = await api.getMe();\n      poolApis.push(api);\n      logger.info(\n        { username: me.username, id: me.id, poolSize: poolApis.length },\n        'Pool bot initialized',\n      );\n    } catch (err) {\n      logger.error({ err }, 'Failed to initialize pool bot');\n    }\n  }\n  if (poolApis.length > 0) {\n    logger.info({ count: poolApis.length }, 'Telegram bot pool ready');\n  }\n}\n\n\u002F**\n * Send a message via a pool bot assigned to the given sender name.\n * Assigns bots round-robin on first use; subsequent messages from the\n * same sender in the same group always use the same bot.\n * On first assignment, renames the bot to match the sender's role.\n *\u002F\nexport async function sendPoolMessage(\n  chatId: string,\n  text: string,\n  sender: string,\n  groupFolder: string,\n): Promise\u003Cvoid> {\n  if (poolApis.length === 0) {\n    \u002F\u002F No pool bots — fall back to main bot\n    await sendTelegramMessage(chatId, text);\n    return;\n  }\n\n  const key = `${groupFolder}:${sender}`;\n  let idx = senderBotMap.get(key);\n  if (idx === undefined) {\n    idx = nextPoolIndex % poolApis.length;\n    nextPoolIndex++;\n    senderBotMap.set(key, idx);\n    \u002F\u002F Rename the bot to match the sender's role, then wait for Telegram to propagate\n    try {\n      await poolApis[idx].setMyName(sender);\n      await new Promise((r) => setTimeout(r, 2000));\n      logger.info({ sender, groupFolder, poolIndex: idx }, 'Assigned and renamed pool bot');\n    } catch (err) {\n      logger.warn({ sender, err }, 'Failed to rename pool bot (sending anyway)');\n    }\n  }\n\n  const api = poolApis[idx];\n  try {\n    const numericId = chatId.replace(\u002F^tg:\u002F, '');\n    const MAX_LENGTH = 4096;\n    if (text.length \u003C= MAX_LENGTH) {\n      await api.sendMessage(numericId, text);\n    } else {\n      for (let i = 0; i \u003C text.length; i += MAX_LENGTH) {\n        await api.sendMessage(numericId, text.slice(i, i + MAX_LENGTH));\n      }\n    }\n    logger.info({ chatId, sender, poolIndex: idx, length: text.length }, 'Pool message sent');\n  } catch (err) {\n    logger.error({ chatId, sender, err }, 'Failed to send pool message');\n  }\n}\n```\n\n### Step 3: Add sender Parameter to MCP Tool\n\nRead `container\u002Fagent-runner\u002Fsrc\u002Fipc-mcp-stdio.ts` and update the `send_message` tool to accept an optional `sender` parameter:\n\nChange the tool's schema from:\n```typescript\n{ text: z.string().describe('The message text to send') },\n```\n\nTo:\n```typescript\n{\n  text: z.string().describe('The message text to send'),\n  sender: z.string().optional().describe('Your role\u002Fidentity name (e.g. \"Researcher\"). When set, messages appear from a dedicated bot in Telegram.'),\n},\n```\n\nAnd update the handler to include `sender` in the IPC data:\n\n```typescript\nasync (args) => {\n    const data: Record\u003Cstring, string | undefined> = {\n      type: 'message',\n      chatJid,\n      text: args.text,\n      sender: args.sender || undefined,\n      groupFolder,\n      timestamp: new Date().toISOString(),\n    };\n\n    writeIpcFile(MESSAGES_DIR, data);\n\n    return { content: [{ type: 'text' as const, text: 'Message sent.' }] };\n  },\n```\n\n### Step 4: Update Host IPC Routing\n\nRead `src\u002Fipc.ts` and make these changes:\n\n1. **Add imports** — add `sendPoolMessage` and `initBotPool` from the Telegram swarm module, and `TELEGRAM_BOT_POOL` from config.\n\n2. **Update IPC message routing** — in `src\u002Fipc.ts`, find where the `sendMessage` dependency is called to deliver IPC messages (inside `processIpcFiles`). The `sendMessage` is passed in via the `IpcDeps` parameter. Wrap it to route Telegram swarm messages through the bot pool:\n\n```typescript\nif (data.sender && data.chatJid.startsWith('tg:')) {\n  await sendPoolMessage(\n    data.chatJid,\n    data.text,\n    data.sender,\n    sourceGroup,\n  );\n} else {\n  await deps.sendMessage(data.chatJid, data.text);\n}\n```\n\nNote: The assistant name prefix is handled by `formatOutbound()` in the router — Telegram channels have `prefixAssistantName = false` so no prefix is added for `tg:` JIDs.\n\n3. **Initialize pool in `main()` in `src\u002Findex.ts`** — after creating the Telegram channel, add:\n\n```typescript\nif (TELEGRAM_BOT_POOL.length > 0) {\n  await initBotPool(TELEGRAM_BOT_POOL);\n}\n```\n\n### Step 5: Update CLAUDE.md Files\n\n#### 5a. Add global message formatting rules\n\nRead `groups\u002Fglobal\u002FCLAUDE.md` and add a Message Formatting section:\n\n```markdown\n## Message Formatting\n\nNEVER use markdown. Only use WhatsApp\u002FTelegram formatting:\n- *single asterisks* for bold (NEVER **double asterisks**)\n- _underscores_ for italic\n- • bullet points\n- ```triple backticks``` for code\n\nNo ## headings. No [links](url). No **double stars**.\n```\n\n#### 5b. Update existing group CLAUDE.md headings\n\nIn any group CLAUDE.md that has a \"WhatsApp Formatting\" section (e.g. `groups\u002Fmain\u002FCLAUDE.md`), rename the heading to reflect multi-channel support:\n\n```\n## WhatsApp Formatting (and other messaging apps)\n```\n\n#### 5c. Add Agent Teams instructions to Telegram groups\n\nFor each Telegram group that will use agent teams, create or update its `groups\u002F{folder}\u002FCLAUDE.md` with these instructions. Read the existing CLAUDE.md first (or `groups\u002Fglobal\u002FCLAUDE.md` as a base) and add the Agent Teams section:\n\n```markdown\n## Agent Teams\n\nWhen creating a team to tackle a complex task, follow these rules:\n\n### CRITICAL: Follow the user's prompt exactly\n\nCreate *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.\n\n### Team member instructions\n\nEach team member MUST be instructed to:\n\n1. *Share progress in the group* via `mcp__deus__send_message` with a `sender` parameter matching their exact role\u002Fcharacter name (e.g., `sender: \"Marine Biologist\"` or `sender: \"Alexander Hamilton\"`). This makes their messages appear from a dedicated bot in the Telegram group.\n2. *Also communicate with teammates* via `SendMessage` as normal for coordination.\n3. Keep group messages *short* — 2-4 sentences max per message. Break longer content into multiple `send_message` calls. No walls of text.\n4. Use the `sender` parameter consistently — always the same name so the bot identity stays stable.\n5. NEVER use markdown formatting. Use ONLY WhatsApp\u002FTelegram formatting: single *asterisks* for bold (NOT **double**), _underscores_ for italic, • for bullets, ```backticks``` for code. No ## headings, no [links](url), no **double asterisks**.\n\n### Example team creation prompt\n\nWhen creating a teammate, include instructions like:\n\n\\```\nYou are the Marine Biologist. When you have findings or updates for the user, send them to the group using mcp__deus__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.\n\\```\n\n### Lead agent behavior\n\nAs the lead agent who created the team:\n\n- You do NOT need to react to or relay every teammate message. The user sees those directly from the teammate bots.\n- Send your own messages only to comment, share thoughts, synthesize, or direct the team.\n- When processing an internal update from a teammate that doesn't need a user-facing response, wrap your *entire* output in `\u003Cinternal>` tags.\n- Focus on high-level coordination and the final synthesis.\n```\n\n### Step 6: Update Environment\n\nAdd pool tokens to `.env`:\n\n```bash\nTELEGRAM_BOT_POOL=TOKEN1,TOKEN2,TOKEN3,...\n```\n\n**Important**: Sync to all required locations:\n\n```bash\ncp .env data\u002Fenv\u002Fenv\n```\n\nAlso add `TELEGRAM_BOT_POOL` to the launchd plist (`~\u002FLibrary\u002FLaunchAgents\u002Fcom.deus.plist`) in the `EnvironmentVariables` dict if using launchd.\n\n### Step 7: Rebuild and Restart\n\n```bash\nnpm run build\n.\u002Fcontainer\u002Fbuild.sh  # Required — MCP tool changed\n# macOS:\nlaunchctl unload ~\u002FLibrary\u002FLaunchAgents\u002Fcom.deus.plist\nlaunchctl load ~\u002FLibrary\u002FLaunchAgents\u002Fcom.deus.plist\n# Linux:\n# systemctl --user restart deus\n```\n\nMust use `unload\u002Fload` (macOS) or `restart` (Linux) because the service env vars changed.\n\n### Step 8: Test\n\nTell the user:\n\n> Send a message in your Telegram group asking for a multi-agent task, e.g.:\n> \"Assemble a team of a researcher and a coder to build me a hello world app\"\n>\n> You should see:\n> - The lead agent (main bot) acknowledging and creating the team\n> - Each subagent messaging from a different bot, renamed to their role\n> - Short, scannable messages from each agent\n>\n> Check logs: `tail -f logs\u002Fdeus.log | grep -i pool`\n\n## Architecture Notes\n\n- Pool bots use Grammy's `Api` class — lightweight, no polling, just send\n- Bot names are set via `setMyName` — changes are global to the bot, not per-chat\n- A 2-second delay after `setMyName` allows Telegram to propagate the name change before the first message\n- Sender→bot mapping is stable within a group (keyed as `{groupFolder}:{senderName}`)\n- Mapping resets on service restart — pool bots get reassigned fresh\n- If pool runs out, bots are reused (round-robin wraps)\n\n## Troubleshooting\n\n### Pool bots not sending messages\n\n1. Verify tokens: `curl -s \"https:\u002F\u002Fapi.telegram.org\u002FbotTOKEN\u002FgetMe\"`\n2. Check pool initialized: `grep \"Pool bot\" logs\u002Fdeus.log`\n3. Ensure all pool bots are members of the Telegram group\n4. Check Group Privacy is disabled for each pool bot\n\n### Bot names not updating\n\nTelegram 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.\n\n### Subagents not using send_message\n\nCheck 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.\n\n## Removal\n\nTo remove Agent Swarm support while keeping basic Telegram:\n\n1. Remove `TELEGRAM_BOT_POOL` from `src\u002Fconfig.ts`\n2. Remove pool code from `src\u002Ftelegram.ts` (`poolApis`, `senderBotMap`, `initBotPool`, `sendPoolMessage`)\n3. Remove pool routing from IPC handler in `src\u002Findex.ts` (revert to plain `sendMessage`)\n4. Remove `initBotPool` call from `main()`\n5. Remove `sender` param from MCP tool in `container\u002Fagent-runner\u002Fsrc\u002Fipc-mcp-stdio.ts`\n6. Remove Agent Teams section from group CLAUDE.md files\n7. Remove `TELEGRAM_BOT_POOL` from `.env`, `data\u002Fenv\u002Fenv`, and launchd plist\u002Fsystemd unit\n8. Rebuild: `npm run build && .\u002Fcontainer\u002Fbuild.sh && launchctl unload ~\u002FLibrary\u002FLaunchAgents\u002Fcom.deus.plist && launchctl load ~\u002FLibrary\u002FLaunchAgents\u002Fcom.deus.plist` (macOS) or `npm run build && .\u002Fcontainer\u002Fbuild.sh && systemctl --user restart deus` (Linux)\n","# Add Agent Swarm to Telegram\n\nThis 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.\n\n**Prerequisite**: Telegram must already be set up via the `\u002Fadd-telegram` skill. If `src\u002Ftelegram.ts` does not exist or `TELEGRAM_BOT_TOKEN` is not configured, tell the user to run `\u002Fadd-telegram` first.\n\n## How It Works\n\n- The **main bot** receives messages and sends lead agent responses (already set up by `\u002Fadd-telegram`)\n- **Pool bots** are send-only — each gets a Grammy `Api` instance (no polling)\n- When a subagent calls `send_message` with a `sender` parameter, the host assigns a pool bot and renames it to match the sender's role\n- Messages appear in Telegram from different bot identities\n\n```\nSubagent calls send_message(text: \"Found 3 results\", sender: \"Researcher\")\n  → MCP writes IPC file with sender field\n  → Host IPC watcher picks it up\n  → Assigns pool bot #2 to \"Researcher\" (round-robin, stable per-group)\n  → Renames pool bot #2 to \"Researcher\" via setMyName\n  → Sends message via pool bot #2's Api instance\n  → Appears in Telegram from \"Researcher\" bot\n```\n\n## Prerequisites\n\n### 1. Create Pool Bots\n\nTell the user:\n\n> I need you to create 3-5 Telegram bots to use as the agent pool. These will be renamed dynamically to match agent roles.\n>\n> 1. Open Telegram and search for `@BotFather`\n> 2. Send `\u002Fnewbot` for each bot:\n>    - Give them any placeholder name (e.g., \"Bot 1\", \"Bot 2\")\n>    - Usernames like `myproject_swarm_1_bot`, `myproject_swarm_2_bot`, etc.\n> 3. Copy all the tokens\n> 4. Add all bots to your Telegram group(s) where you want agent teams\n\nWait for user to provide the tokens.\n\n### 2. Disable Group Privacy for Pool Bots\n\nTell the user:\n\n> **Important**: Each pool bot needs Group Privacy disabled so it can send messages in groups.\n>\n> For each pool bot in `@BotFather`:\n> 1. Send `\u002Fmybots` and select the bot\n> 2. Go to **Bot Settings** > **Group Privacy** > **Turn off**\n>\n> Then add all pool bots to your Telegram group(s).\n\n## Implementation\n\n### Step 1: Update Configuration\n\nRead `src\u002Fconfig.ts` and add the bot pool config near the other Telegram exports:\n\n```typescript\nexport const TELEGRAM_BOT_POOL = (process.env.TELEGRAM_BOT_POOL || '')\n  .split(',')\n  .map((t) => t.trim())\n  .filter(Boolean);\n```\n\n### Step 2: Add Bot Pool to Telegram Module\n\nRead `src\u002Ftelegram.ts` and add the following:\n\n1. **Update imports** — add `Api` to the Grammy import:\n\n```typescript\nimport { Api, Bot } from 'grammy';\n```\n\n2. **Add pool state** after the existing `let bot` declaration:\n\n```typescript\n\u002F\u002F Bot pool for agent teams: send-only Api instances (no polling)\nconst poolApis: Api[] = [];\n\u002F\u002F Maps \"{groupFolder}:{senderName}\" → pool Api index for stable assignment\nconst senderBotMap = new Map\u003Cstring, number>();\nlet nextPoolIndex = 0;\n```\n\n3. **Add pool functions** — place these before the `isTelegramConnected` function:\n\n```typescript\n\u002F**\n * Initialize send-only Api instances for the bot pool.\n * Each pool bot can send messages but doesn't poll for updates.\n *\u002F\nexport async function initBotPool(tokens: string[]): Promise\u003Cvoid> {\n  for (const token of tokens) {\n    try {\n      const api = new Api(token);\n      const me = await api.getMe();\n      poolApis.push(api);\n      logger.info(\n        { username: me.username, id: me.id, poolSize: poolApis.length },\n        'Pool bot initialized',\n      );\n    } catch (err) {\n      logger.error({ err }, 'Failed to initialize pool bot');\n    }\n  }\n  if (poolApis.length > 0) {\n    logger.info({ count: poolApis.length }, 'Telegram bot pool ready');\n  }\n}\n\n\u002F**\n * Send a message via a pool bot assigned to the given sender name.\n * Assigns bots round-robin on first use; subsequent messages from the\n * same sender in the same group always use the same bot.\n * On first assignment, renames the bot to match the sender's role.\n *\u002F\nexport async function sendPoolMessage(\n  chatId: string,\n  text: string,\n  sender: string,\n  groupFolder: string,\n): Promise\u003Cvoid> {\n  if (poolApis.length === 0) {\n    \u002F\u002F No pool bots — fall back to main bot\n    await sendTelegramMessage(chatId, text);\n    return;\n  }\n\n  const key = `${groupFolder}:${sender}`;\n  let idx = senderBotMap.get(key);\n  if (idx === undefined) {\n    idx = nextPoolIndex % poolApis.length;\n    nextPoolIndex++;\n    senderBotMap.set(key, idx);\n    \u002F\u002F Rename the bot to match the sender's role, then wait for Telegram to propagate\n    try {\n      await poolApis[idx].setMyName(sender);\n      await new Promise((r) => setTimeout(r, 2000));\n      logger.info({ sender, groupFolder, poolIndex: idx }, 'Assigned and renamed pool bot');\n    } catch (err) {\n      logger.warn({ sender, err }, 'Failed to rename pool bot (sending anyway)');\n    }\n  }\n\n  const api = poolApis[idx];\n  try {\n    const numericId = chatId.replace(\u002F^tg:\u002F, '');\n    const MAX_LENGTH = 4096;\n    if (text.length \u003C= MAX_LENGTH) {\n      await api.sendMessage(numericId, text);\n    } else {\n      for (let i = 0; i \u003C text.length; i += MAX_LENGTH) {\n        await api.sendMessage(numericId, text.slice(i, i + MAX_LENGTH));\n      }\n    }\n    logger.info({ chatId, sender, poolIndex: idx, length: text.length }, 'Pool message sent');\n  } catch (err) {\n    logger.error({ chatId, sender, err }, 'Failed to send pool message');\n  }\n}\n```\n\n### Step 3: Add sender Parameter to MCP Tool\n\nRead `container\u002Fagent-runner\u002Fsrc\u002Fipc-mcp-stdio.ts` and update the `send_message` tool to accept an optional `sender` parameter:\n\nChange the tool's schema from:\n```typescript\n{ text: z.string().describe('The message text to send') },\n```\n\nTo:\n```typescript\n{\n  text: z.string().describe('The message text to send'),\n  sender: z.string().optional().describe('Your role\u002Fidentity name (e.g. \"Researcher\"). When set, messages appear from a dedicated bot in Telegram.'),\n},\n```\n\nAnd update the handler to include `sender` in the IPC data:\n\n```typescript\nasync (args) => {\n    const data: Record\u003Cstring, string | undefined> = {\n      type: 'message',\n      chatJid,\n      text: args.text,\n      sender: args.sender || undefined,\n      groupFolder,\n      timestamp: new Date().toISOString(),\n    };\n\n    writeIpcFile(MESSAGES_DIR, data);\n\n    return { content: [{ type: 'text' as const, text: 'Message sent.' }] };\n  },\n```\n\n### Step 4: Update Host IPC Routing\n\nRead `src\u002Fipc.ts` and make these changes:\n\n1. **Add imports** — add `sendPoolMessage` and `initBotPool` from the Telegram swarm module, and `TELEGRAM_BOT_POOL` from config.\n\n2. **Update IPC message routing** — in `src\u002Fipc.ts`, find where the `sendMessage` dependency is called to deliver IPC messages (inside `processIpcFiles`). The `sendMessage` is passed in via the `IpcDeps` parameter. Wrap it to route Telegram swarm messages through the bot pool:\n\n```typescript\nif (data.sender && data.chatJid.startsWith('tg:')) {\n  await sendPoolMessage(\n    data.chatJid,\n    data.text,\n    data.sender,\n    sourceGroup,\n  );\n} else {\n  await deps.sendMessage(data.chatJid, data.text);\n}\n```\n\nNote: The assistant name prefix is handled by `formatOutbound()` in the router — Telegram channels have `prefixAssistantName = false` so no prefix is added for `tg:` JIDs.\n\n3. **Initialize pool in `main()` in `src\u002Findex.ts`** — after creating the Telegram channel, add:\n\n```typescript\nif (TELEGRAM_BOT_POOL.length > 0) {\n  await initBotPool(TELEGRAM_BOT_POOL);\n}\n```\n\n### Step 5: Update CLAUDE.md Files\n\n#### 5a. Add global message formatting rules\n\nRead `groups\u002Fglobal\u002FCLAUDE.md` and add a Message Formatting section:\n\n```markdown\n## Message Formatting\n\nNEVER use markdown. Only use WhatsApp\u002FTelegram formatting:\n- *single asterisks* for bold (NEVER **double asterisks**)\n- _underscores_ for italic\n- • bullet points\n- ```triple backticks``` for code\n\nNo ## headings. No [links](url). No **double stars**.\n```\n\n#### 5b. Update existing group CLAUDE.md headings\n\nIn any group CLAUDE.md that has a \"WhatsApp Formatting\" section (e.g. `groups\u002Fmain\u002FCLAUDE.md`), rename the heading to reflect multi-channel support:\n\n```\n## WhatsApp Formatting (and other messaging apps)\n```\n\n#### 5c. Add Agent Teams instructions to Telegram groups\n\nFor each Telegram group that will use agent teams, create or update its `groups\u002F{folder}\u002FCLAUDE.md` with these instructions. Read the existing CLAUDE.md first (or `groups\u002Fglobal\u002FCLAUDE.md` as a base) and add the Agent Teams section:\n\n```markdown\n## Agent Teams\n\nWhen creating a team to tackle a complex task, follow these rules:\n\n### CRITICAL: Follow the user's prompt exactly\n\nCreate *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.\n\n### Team member instructions\n\nEach team member MUST be instructed to:\n\n1. *Share progress in the group* via `mcp__deus__send_message` with a `sender` parameter matching their exact role\u002Fcharacter name (e.g., `sender: \"Marine Biologist\"` or `sender: \"Alexander Hamilton\"`). This makes their messages appear from a dedicated bot in the Telegram group.\n2. *Also communicate with teammates* via `SendMessage` as normal for coordination.\n3. Keep group messages *short* — 2-4 sentences max per message. Break longer content into multiple `send_message` calls. No walls of text.\n4. Use the `sender` parameter consistently — always the same name so the bot identity stays stable.\n5. NEVER use markdown formatting. Use ONLY WhatsApp\u002FTelegram formatting: single *asterisks* for bold (NOT **double**), _underscores_ for italic, • for bullets, ```backticks``` for code. No ## headings, no [links](url), no **double asterisks**.\n\n### Example team creation prompt\n\nWhen creating a teammate, include instructions like:\n\n\\```\nYou are the Marine Biologist. When you have findings or updates for the user, send them to the group using mcp__deus__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.\n\\```\n\n### Lead agent behavior\n\nAs the lead agent who created the team:\n\n- You do NOT need to react to or relay every teammate message. The user sees those directly from the teammate bots.\n- Send your own messages only to comment, share thoughts, synthesize, or direct the team.\n- When processing an internal update from a teammate that doesn't need a user-facing response, wrap your *entire* output in `\u003Cinternal>` tags.\n- Focus on high-level coordination and the final synthesis.\n```\n\n### Step 6: Update Environment\n\nAdd pool tokens to `.env`:\n\n```bash\nTELEGRAM_BOT_POOL=TOKEN1,TOKEN2,TOKEN3,...\n```\n\n**Important**: Sync to all required locations:\n\n```bash\ncp .env data\u002Fenv\u002Fenv\n```\n\nAlso add `TELEGRAM_BOT_POOL` to the launchd plist (`~\u002FLibrary\u002FLaunchAgents\u002Fcom.deus.plist`) in the `EnvironmentVariables` dict if using launchd.\n\n### Step 7: Rebuild and Restart\n\n```bash\nnpm run build\n.\u002Fcontainer\u002Fbuild.sh  # Required — MCP tool changed\n# macOS:\nlaunchctl unload ~\u002FLibrary\u002FLaunchAgents\u002Fcom.deus.plist\nlaunchctl load ~\u002FLibrary\u002FLaunchAgents\u002Fcom.deus.plist\n# Linux:\n# systemctl --user restart deus\n```\n\nMust use `unload\u002Fload` (macOS) or `restart` (Linux) because the service env vars changed.\n\n### Step 8: Test\n\nTell the user:\n\n> Send a message in your Telegram group asking for a multi-agent task, e.g.:\n> \"Assemble a team of a researcher and a coder to build me a hello world app\"\n>\n> You should see:\n> - The lead agent (main bot) acknowledging and creating the team\n> - Each subagent messaging from a different bot, renamed to their role\n> - Short, scannable messages from each agent\n>\n> Check logs: `tail -f logs\u002Fdeus.log | grep -i pool`\n\n## Architecture Notes\n\n- Pool bots use Grammy's `Api` class — lightweight, no polling, just send\n- Bot names are set via `setMyName` — changes are global to the bot, not per-chat\n- A 2-second delay after `setMyName` allows Telegram to propagate the name change before the first message\n- Sender→bot mapping is stable within a group (keyed as `{groupFolder}:{senderName}`)\n- Mapping resets on service restart — pool bots get reassigned fresh\n- If pool runs out, bots are reused (round-robin wraps)\n\n## Troubleshooting\n\n### Pool bots not sending messages\n\n1. Verify tokens: `curl -s \"https:\u002F\u002Fapi.telegram.org\u002FbotTOKEN\u002FgetMe\"`\n2. Check pool initialized: `grep \"Pool bot\" logs\u002Fdeus.log`\n3. Ensure all pool bots are members of the Telegram group\n4. Check Group Privacy is disabled for each pool bot\n\n### Bot names not updating\n\nTelegram 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.\n\n### Subagents not using send_message\n\nCheck 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.\n\n## Removal\n\nTo remove Agent Swarm support while keeping basic Telegram:\n\n1. Remove `TELEGRAM_BOT_POOL` from `src\u002Fconfig.ts`\n2. Remove pool code from `src\u002Ftelegram.ts` (`poolApis`, `senderBotMap`, `initBotPool`, `sendPoolMessage`)\n3. Remove pool routing from IPC handler in `src\u002Findex.ts` (revert to plain `sendMessage`)\n4. Remove `initBotPool` call from `main()`\n5. Remove `sender` param from MCP tool in `container\u002Fagent-runner\u002Fsrc\u002Fipc-mcp-stdio.ts`\n6. Remove Agent Teams section from group CLAUDE.md files\n7. Remove `TELEGRAM_BOT_POOL` from `.env`, `data\u002Fenv\u002Fenv`, and launchd plist\u002Fsystemd unit\n8. Rebuild: `npm run build && .\u002Fcontainer\u002Fbuild.sh && launchctl unload ~\u002FLibrary\u002FLaunchAgents\u002Fcom.deus.plist && launchctl load ~\u002FLibrary\u002FLaunchAgents\u002Fcom.deus.plist` (macOS) or `npm run build && .\u002Fcontainer\u002Fbuild.sh && systemctl --user restart deus` (Linux)\n",{"name":5,"description":7,"disable-model-invocation":49},true,"main","2f24c38a2ae42d3161492f0729975b286452f4b4",".claude\u002Fskills\u002Fadd-telegram-swarm",7,"https:\u002F\u002Fgithub.com\u002Fsliamh11\u002FDeus","https:\u002F\u002Fgithub.com\u002Fsliamh11","2026-11-10 03:41:56",{"source":8,"skillName":5,"commands":58,"prompt":68,"zipAvailable":49},[59,62,65],{"tool":60,"command":61},"npx","npx skills add sliamh11\u002FDeus --skill add-telegram-swarm",{"tool":63,"command":64},"bunx","bunx skills add sliamh11\u002FDeus --skill add-telegram-swarm",{"tool":66,"command":67},"pnpm","pnpm dlx skills add sliamh11\u002FDeus --skill add-telegram-swarm","安装技能，技能地址：https:\u002F\u002Fgithub.com\u002Fsliamh11\u002FDeus\u002Fblob\u002Fmain\u002F.claude\u002Fskills\u002Fadd-telegram-swarm\u002FSKILL.md",[],[71,96,116,130,140,154],{"skillNo":72,"name":73,"nameZh":6,"description":74,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":75,"categories":76,"keywords":77,"publishedAt":44},"01M6H3ZC87AJDM8566RP3CQ6BF","status","Quick read-only health check — session context, workspace mounts, tool availability, and task snapshot. Use when the user asks for system status or runs \u002Fstatus.","https:\u002F\u002Fgithub.com\u002Fsliamh11\u002FDeus\u002Fblob\u002Fmain\u002Fcontainer\u002Fskills\u002Fstatus\u002FSKILL.md",[],[73,78,79,80,81,82,83,84,85,86,87,35,88,89,90,91,92,93,94,95],"health","snapshot","for","read-only","quick","availability","session context","task","health check","only","session","user","context","read","check","workspace","tool","system",{"skillNo":97,"name":98,"nameZh":6,"description":99,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":100,"categories":101,"keywords":107,"publishedAt":44},"01M6H3Z8SK38PVH2B0W7NBP6ZP","resume","Load context from permanent memory and recent session logs. Tiered loading — CLAUDE.md full, session logs frontmatter-only by default.","https:\u002F\u002Fgithub.com\u002Fsliamh11\u002FDeus\u002Fblob\u002Fmain\u002Fcontainer\u002Fskills\u002Fresume\u002FSKILL.md",[102],{"categoryNo":103,"categoryName":104,"taxonomyType":105,"taxonomyTypeDesc":106},"01JBSEEDCAT000000000000038","人力资源","PROFESSION","适用职业",[98,108,109,110,111,112,113,87,88,90,114,115],"memory","load","loading","full","session logs","default","logs","frontmatter",{"skillNo":117,"name":118,"nameZh":6,"description":119,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":120,"categories":121,"keywords":122,"publishedAt":44},"01M6H3Z56ZBPKX77THMWSSHZCW","preserve","Scan the current conversation and silently save anything worth permanent memory to CLAUDE.md. Keeps the file compact — structured key:value, no prose.","https:\u002F\u002Fgithub.com\u002Fsliamh11\u002FDeus\u002Fblob\u002Fmain\u002Fcontainer\u002Fskills\u002Fpreserve\u002FSKILL.md",[],[118,123,124,125,126,127,128,129,108],"structured","compact","scan","conversation","prose","save","file",{"skillNo":131,"name":132,"nameZh":6,"description":133,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":134,"categories":135,"keywords":136,"publishedAt":44},"01M6H3Z1AKK58CWWY762YD3X9A","compress","Save the current session to the vault. Writes a compact session log with a TL;DR frontmatter (always) and full details below (when useful).","https:\u002F\u002Fgithub.com\u002Fsliamh11\u002FDeus\u002Fblob\u002Fmain\u002Fcontainer\u002Fskills\u002Fcompress\u002FSKILL.md",[],[132,137,115,111,124,138,88,128,139],"with","log","vault",{"skillNo":141,"name":142,"nameZh":6,"description":143,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":144,"categories":145,"keywords":146,"publishedAt":44},"01M6H3YX6STE1ZJC06EKWMY0M6","capabilities","Show what this Deus instance can do — installed skills, available tools, and system info. Read-only. Use when the user asks what the bot can do, what's installed, or runs \u002Fcapabilities.","https:\u002F\u002Fgithub.com\u002Fsliamh11\u002FDeus\u002Fblob\u002Fmain\u002Fcontainer\u002Fskills\u002Fcapabilities\u002FSKILL.md",[],[142,147,36,95,81,148,149,150,151,152,153,87,35,89,91],"Tools","can","system info","info","what the","do","show",{"skillNo":155,"name":156,"nameZh":6,"description":157,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":158,"categories":159,"keywords":161,"publishedAt":44},"01M6H3YSNXYAR7NC1TZPFCANZN","agent-browser","Browse the web for any task — research topics, read articles, interact with web apps, fill forms, take screenshots, extract data, and test web pages. Use whenever a browser would be useful, not just when the user explicitly asks.","https:\u002F\u002Fgithub.com\u002Fsliamh11\u002FDeus\u002Fblob\u002Fmain\u002Fcontainer\u002Fskills\u002Fagent-browser\u002FSKILL.md",[160],{"categoryNo":19,"categoryName":20,"taxonomyType":21,"taxonomyTypeDesc":22},[162,163,164,80,137,165,166,167,85,168,35,91,169,170,171,172,173,174,175,176],"agent browser","browser","take","apps","pages","forms","test web","just","Bash","browse","data","web","test","research","screenshots"]