Skip to content

Telegram Multi-Fleet Hub ​

Experimental. Steer every running fleet on your machine from one Telegram bot. Each fleet gets its own forum topic in a private group. The General topic is the Control channel: it talks to the control plane and runs hub commands.

The hub runs inside the control plane (agents-fleet --control-plane) and uses the control-plane bot. It replaces the "one bot per fleet" setup from Telegram Remote Steering, although that setup still works.

How it works ​

 Telegram forum group ("Fleet Hub")
 ├─ General  = Control ── /fleets /pending /stop … + chat with the control plane
 ├─ 🟢 api-server       ── fleet A: prompts in, replies / asks / task completions out
 ├─ 🟡 web-frontend     ── fleet B (waiting on an ask)
 └─ ⚫ data-pipeline    ── fleet C (stopped)
        ▲
        │  one bot token (control plane)
 agents-fleet --control-plane ──(per-fleet control socket, token-gated)──► agents-fleet --hub
  • Fleets opt in. A fleet joins only when it starts with --hub (or AGENTS_FLEET_TELEGRAM_HUB_ATTACH=1, or "telegramHub": {"attach": true} in ~/.fleet/config.json). Opting in turns on the fleet's control socket.
  • You approve every fleet once (TOFU). Before it reads any fleet token or opens a connection, the hub validates the fleet's discovery descriptor and asks you in General. This includes fleets the control plane started itself (start_fleet): they are not trusted automatically. Approve once per session or per folder.
  • Only you can act. The hub checks the sender's from.id on every message and every button tap, not just the chat. Other group members are ignored. A button tap counts only in the chat where the hub posted that button.
  • Topic text goes straight to the fleet. No LLM sits between the topic and the fleet. Text typed in a topic becomes a prompt for that fleet. Forwarded messages are refused everywhere in the hub (topics, General and your DM): type or paste the text instead.

Setup runbook ​

1. Create the control-plane bot ​

  1. In Telegram, talk to @BotFather, send /newbot and copy the token.

  2. Use a different bot from any fleet's --telegram bot. The control plane refuses to start when the two tokens are equal.

  3. Export the token on the control-plane host:

    bash
    export AGENTS_FLEET_CONTROL_PLANE_TELEGRAM_TOKEN='<token>'   # PowerShell: $env:AGENTS_FLEET_CONTROL_PLANE_TELEGRAM_TOKEN='<token>'
    agents-fleet --control-plane

2. Pair with the bot in a DM ​

bash
agents-fleet bot pair --control-plane

The command prints /start <token> once. Open a private chat with the bot and send that line within 5 minutes. The bot answers ✅ Paired!. This DM pairing makes you the operator. Pairing only works in a DM; the bot ignores /start in groups.

3. Create the forum group ​

  1. Create a new group and add the bot.
  2. In the group settings, turn on Topics. This converts the group into a forum supergroup.
  3. Promote the bot to administrator with the Manage Topics right.
  4. In your own admin settings for this group, turn Remain Anonymous off. An anonymous admin (or a post "as channel") arrives as GroupAnonymousBot, not as you, so the hub ignores it.

4. Bind the group ​

Send /hubbind in the group. If the bot is not an admin yet and still has privacy mode on, Telegram only delivers commands addressed to it: send /hubbind@<botname> instead.

The bot then DMs you: "Bind group '…' as your fleet hub?". Tap ✅ Bind. The prompt expires after 5 minutes. A /hubbind from anyone who is not DM-paired gets no reply.

5. Verify ​

Send /hub in General. A correct setup shows:

mode=forum · chat=-100… · operator=<your Telegram id>

mode=fallback means a right is missing: Topics are off, or the bot is not an admin with Manage Topics. The hub posts the fix in General and re-checks every 10 minutes. It also re-checks when the bot's rights change.

6. Start fleets with --hub ​

bash
cd ~/src/api-server && agents-fleet --hub

Or persist the opt-in in that fleet's ~/.fleet/config.json (or its FLEET_HOME):

json
{ "telegramHub": { "attach": true } }

Fleets started by the control plane (start_fleet) opt in automatically. They still never receive any Telegram token, and they still need your approval (step 7).

7. Approve each fleet once ​

For each new fleet, including the ones the control plane started, a prompt appears in General with its name, folder, FLEET_HOME, pid and start time:

ButtonEffect
✅ Approve sessionTrust this folder for this session (bound to the session id; the name is only shown). A different session in the same folder asks again, even if it reuses the name.
📁 Approve folderTrust any session in this folder and FLEET_HOME.
⛔ DenyIgnore this fleet. Revoke the decision with /hub trust revoke <n>.

The hub connects only after you approve. It then creates the fleet's topic, named after the session name or else the folder name.

Using the hub ​

In a fleet topic ​

You doWhat happens
Type textSent to that fleet as a prompt. If the fleet is not running, you get ⚫ fleet not running — message not delivered.
Tap an ask / confirm buttonAnswers that fleet's pending request. Many requests can be pending at once; each tap resolves only its own request.
Reply (Telegram "Reply") to a freeform askThe reply text is the answer.
/verbose on|offAlso post tool calls, hub activity and terminal input (⌨️ terminal: …) for this topic.
/hubHub diagnostics.
Any other /commandSent to the fleet as typed. Prefix // to send a command the hub would otherwise handle (//verbose sends /verbose). Dangerous fleet commands are blocked (see below).

Commands blocked from Telegram. These fleet slash commands change configuration, secrets, worktrees, loops, approvals or the process itself, so the hub refuses them in a topic (also with the // prefix): /mcp, /config, /bot-set-token, /bot-clear-token, /worktree, /loop, /loop-target, /approve, /exit, /quit, /q. Run them in the fleet's terminal. To stop a fleet from Telegram, use /stop <fleet> in General.

The fleet enforces this itself for anything the hub sends: it resolves the command to its real name first, so aliases, other casing and a @bot suffix are refused too (/wt, /WORKTREE, /worktree@bot). Commands that need a confirmation tap on the regular Telegram bot are also refused from the hub, because the hub has no confirm step for them: for example /root, /compete, /commit, /team-repo, /restart and skill/crew workflow commands. Run those in the fleet's terminal as well. You get 🚫 /<command> is blocked from Telegram — run it in the fleet's terminal.

Some read-only commands also have subcommands that change things. From Telegram only their read-only forms run (the subcommand must be typed exactly as shown; anything else is refused with the same 🚫 message):

Command (and aliases)Allowed from Telegram
/crew/crew list (use /crews for the overview)
/wfno argument, status, help
/workflowsno argument, list, status, help
/learn (/intelligence)no argument, overview, steering, shadows
/fleet-intel (/intel)no argument, summary, agents, failures, tokens, stats, skills, search, insights
/skill (/sk), /skillsno argument, list (/skills takes none)
/teamno argument, list

Only text that starts with / is treated as a command. Text with a leading space ( /do x) reaches the fleet as a plain prompt and cannot run a command. The same policy applies when the control-plane assistant drives a fleet (drive_fleet_prompt): it cannot run blocked or confirmation-required commands or mutating subcommands either.

Fleet replies, asks, confirms, errors and task completions are posted into the fleet's topic. If you answer an ask elsewhere first (terminal, another channel), the topic message is edited to ↪ resolved elsewhere.

The topic title shows the fleet status: ⚫ stopped > 🟡 waiting for you > 🔴 error > 🟢 working > ⚪ idle. Title edits are debounced.

In General (Control) ​

CommandDescription
/fleetsOne line per fleet: status, workers, tasks, tokens, context %, topic link.
/pendingEvery unanswered ask/confirm across fleets, oldest first, with links.
/stop <fleet>Stop a fleet. Requires 3 taps within 60 s. Control-plane fleets stop through the supervisor; others get SIGTERM. On Windows this is a hard terminate.
/pause <fleet> / /resume <fleet>Pause / resume task dispatch (/pause-dispatch).
/verbose <fleet> on|offVerbosity for a fleet's topic.
/use <fleet> / /use / /use noneFallback mode only: send your plain text to a fleet, show the selection, or clear it.
/hubMode, operator, discovery stats, invalid descriptors with reasons, last probe, queue depth and oldest item per class, unauthorized counters.
/hub trust / /hub trust revoke <n>List fleet approvals / revoke one (the fleet detaches and asks again).
/hub unbindUnbind the group. Topics are kept, closed.
Anything elseGoes to the control-plane LLM, as in a DM with the bot.

Your DM with the bot keeps working as the control-plane chat. Fleet traffic goes only to the group.

Behaviour to know ​

  • Stale messages are not executed. Telegram re-delivers messages that were sent while the hub was down. Prompts, /stop and Control commands older than maxInboundAgeMs (default 10 min) are rejected with ⌛ stale … not delivered.
  • Order is kept. Two quick messages in the same chat reach the fleet in the order you sent them. Button taps are handled at once and never wait behind a message.
  • Everything posted is redacted. Secrets and PII are masked in all hub output: messages, edits, button labels and topic names.
  • Stale links are retired. When a fleet's pid is reused or its descriptor goes stale, the hub drops the old link and treats the next start as a new fleet.
  • Rate limits. Telegram allows about 20 messages per minute per group. All topics and General share one queue (AGENTS_FLEET_TELEGRAM_HUB_RATE_PER_MIN, default 20) with round-robin fairness. Asks, confirms and errors go first and are never dropped. Replies are coalesced. Status and verbose posts are sent silently (no notification). Control-plane replies in your DM are not part of this budget.
  • Stopping the control plane. On a clean stop the control plane waits up to 5 s for messages it already received to finish routing. A message Telegram already handed over can be lost if the process is killed hard.
  • Restarts never repost history. After a control-plane or fleet restart, the hub reuses the same topic and approval. It posts only what is new. An ask that was posted before the restart keeps working through its original button.
  • Fleet also running --telegram. Its asks appear in both its own bot DM and the hub topic. The first answer wins, and the other copy shows ↪ resolved elsewhere. The fleet prints a warning at startup.
  • The control-plane hub dies, fleets keep running. A --hub fleet keeps its token-gated control socket open, and its descriptor keeps advertising hubAttach. A restarted control plane re-attaches without prompting. To close the socket, restart the fleet without --hub.
  • Topics. If you delete a fleet's topic, the hub creates a new one on the next post. If you close a topic, the hub only posts asks and confirms there, and reopens it to do so.
  • Attachments. Files are accepted only in General (the control plane downloads them). In a fleet topic they are not delivered.

Turning it off ​

GoalHow
Disable the hub (legacy control-plane bot)AGENTS_FLEET_TELEGRAM_HUB=0, or "enabled": false in <control-plane home>/hub.json.
Detach one fleetRestart it without --hub (or AGENTS_FLEET_TELEGRAM_HUB_ATTACH=0), or /hub trust revoke <n>.
Unbind the group/hub unbind in General or your DM.
Revoke your pairingRemove it from <control-plane home>/.fleet/telegram.json (bot revoke has no --control-plane variant yet). Pairings are re-read on every update.

All settings are listed in Configuration.

Troubleshooting ​

SymptomFix
/hubbind gets no answerPair in a DM first (bot pair --control-plane). If the bot is not an admin yet, use /hubbind@<botname>.
Your messages in the group are ignoredTurn off Remain Anonymous and don't post "as channel". The hub DMs you a hint once.
/hub shows mode=fallbackTurn on Topics; promote the bot to admin with Manage Topics.
A fleet never shows upStart it with --hub. Check /hub for invalid descriptors (with the reason) or a pending approval in General. A denied fleet needs /hub trust revoke <n>.
A fleet asks for approval againIt runs a new session and was approved per session. Use 📁 Approve folder to stop this.
A fleet is listed under "invalid" in /hubThe registry entry failed validation (dead pid, paths outside its FLEET_HOME, socket path mismatch …). The hub never connects to it. The group only gets one summary notice (⚠️ N invalid fleet descriptor(s) ignored — see /hub) at most every 10 minutes; details are only in /hub. A planted descriptor is expected to end up here.
A forwarded message is refusedForwards are refused in hub chats. Type or paste the text.
/mcp (or another command) is refused in a topicIt is on the blocked list. Run it in the fleet's terminal.
authentication failed (token rotated?)The fleet's daemon.token changed. Restart the fleet; the hub does not retry.
Constant 429Lower the volume (/verbose off) or AGENTS_FLEET_TELEGRAM_HUB_RATE_PER_MIN.

Security notes ​

  • The hub reads a fleet's daemon.token only after the descriptor passes validation and you approve the fleet. The token is never written to hub state or logs.
  • On Windows, named pipes carry no ACL, so the token file is the only boundary. A local process that squats a fleet's pipe name before the fleet starts could receive the token in the hello frame. Known v1 limitation: the control-socket hello sends the raw token; a challenge-response handshake is tracked in #833. See the Telegram threat model and SECURITY.md.
  • v1 has one operator per group.