plexward

CONNECTORS / TELEGRAM

Telegram

Telegram is the human in human-in-the-loop. It is not a data source: it is the gate every outbound action passes through, and the surface where per-entity briefings land on your phone.

Service layerPer-entity briefing topics and the Approve / Edit / Reject gate.

Set this up second, right after GitHub. Without it, briefings have nowhere to go and nothing that can send a message is safe to enable.

What it gives Plexward

  • Notifications out. Any script, loop or agent can push a line to a named channel. Normal priority coalesces in a short window per channel; urgent bypasses the coalescing.
  • One topic per entity. In forum mode each channel routes to its own topic in a private group, so you can mute one company on your phone without muting the rest.
  • Approvals in. An approval request arrives with Approve, Edit, Reject and Details buttons. The calling agent blocks until you press one. Edit captures your corrected text and uses that.
  • Loop control. Pause or resume a named agent loop from your phone.

Layer

service. One bot and one bridge process serve the whole deployment; entities are channels or topics inside it. Not probeable by claude mcp list, so it reports unprobed. Its health check is the bridge's own status.

Declare it as:

{ "name": "Telegram", "layer": "service", "svc": "Briefings ยท Approvals", "account": "acme topic" }

Prerequisites

  • A Telegram account.
  • A bot created through BotFather, with privacy mode left on.
  • Python, standard library only. There is nothing to pip install.
  • A process manager to keep the bridge alive.

Setup steps

1. Create the bot. Talk to BotFather, /newbot, take the token. Store it outside the repo:

mkdir -p ~/.config/secrets
cat > ~/.config/secrets/telegram.env <<'ENV'
TELEGRAM_BOT_TOKEN=<token>
ENV
chmod 600 ~/.config/secrets/telegram.env

2. Configure the bridge.

cd tg-bridge
cp config.example.json config.json

Point env_file at the file you just wrote. Leave mode as legacy and the allowlist empty for now.

3. Run it.

pm2 start ecosystem.config.js && pm2 save
pm2 logs tg-bridge

With an empty allowlist the bridge is in setup mode and answers only /id.

4. Allowlist yourself. Send /id to the bot from your own account, put the returned numeric id into allowlist_user_ids, and restart. Config is also re-read every minute or so.

Allowlist, not chat membership. Being in the group is not trust. Only allowlisted user ids can press approval buttons or run commands; everyone else gets /id and nothing more.

5. Optional but recommended: forum mode, one topic per entity.

  1. Create a new private Telegram group, just you.
  2. Group profile, Edit, Topics, on. This upgrades it to a forum supergroup.
  3. Add your bot and promote it to admin with at least Manage topics.
  4. Find the group's chat id (a negative number): send any message in the group and read it from the bridge's logs.
  5. Put it in config.json as forum_chat_id.
  6. Run the topic setup script. It creates one topic per entity slug, read from the registry, plus approvals, system and general topics, and writes the topic id map back into the config. It is idempotent, so re-run it after adding an entity.
  7. Set "mode": "forum" and restart. Legacy mode stays one edit away.

6. Wire the callers. Briefings, digests and the comms monitor send through the same bot token with their own senders; one bot serves the whole ecosystem. Agent approval gates request an approval and then wait on it.

Per-entity routing

Each entity gets one topic, identified by a topic id in the bridge config and by a matching field in the entity's registry record. Record it in the brain only as a name, never as a raw id:

## Owned Integrations
| Service | Scope | Notes |
|---|---|---|
| Telegram (tg-bridge) | Briefings, approvals | Topic: `acme`. All briefs and approvals for this entity land there. |

Adding an entity means re-running the topic setup script. Skipping that step is the usual reason a new entity's daily brief seems to run but never appears.

Verify

tg-bridge/bin/tg-notify acme "verify: acme topic is wired"

Expected: the line appears in the entity's topic within seconds, prefixed with the channel name in legacy mode.

For the gate, run one approval end to end:

tg-bridge/bin/mc-approve request '{"title":"verify","body":"press Reject"}'
tg-bridge/bin/mc-approve wait <id>

Press Reject and confirm the waiting command exits with the rejection code. Exit codes are the contract: approved, edited, rejected, expired, and still pending at timeout each have their own.

Common failures

  • A new entity has no topic. The brief runs, the send fails, and the only evidence is a log line. Re-run the topic setup script after every entity you add.
  • The bot cannot create topics. It needs admin with Manage topics in the forum group. Promoting it after the fact is fine; re-run the setup.
  • Rotating the token breaks everything at once. BotFather invalidates the old token immediately, and the briefing and comms senders use the same token. Update the env file and restart every sender in the same sitting.
  • Coalescing looks like lost messages. Normal-priority notifications within the same window are merged per channel. If you need a line to arrive alone and now, mark it urgent.
  • Nothing sends and nothing errors loudly. Check the allowlist first. An empty allowlist puts the bridge back in setup mode.
  • Group chat id sign. Supergroup ids are negative and long. Copying one without the leading minus produces a bot that silently talks to nobody.

Security model

Read this before you expose anything.

  • Only allowlisted user ids may press buttons or issue commands. Membership in the group is not trust.
  • All inbound Telegram text is untrusted. Corrections captured through the Edit button are stored as opaque data with parameterized SQL, never passed through a shell, never fed back into an agent prompt as instructions.
  • Loop pause and resume shell out to a fixed path with a strict name pattern. No free text ever reaches a shell.
  • Approvals and pending edit windows persist in a local database, so a restart loses nothing.

What Plexward does with it once connected

Every per-entity loop ends by sending a short summary to that entity's topic: a four or five line daily brief, a weekly digest, a document-freshness result, a drift warning when a connector goes red.

Every outbound message drafted for WhatsApp, and every other action an agent is not allowed to take unsupervised, becomes an approval request in the approvals topic. The agent blocks until you answer. If the bridge is down, the action fails closed rather than proceeding.

Setup detail lives in tg-bridge/SETUP.md and the command surface in tg-bridge/README.md.

Every connector, one convention.

Plexward keeps the bookkeeping: which entity is supposed to have which connector, whether it is actually working right now, and what an agent is allowed to do with it.