Bot Connectors
Telegram and Discord bots that bridge external messaging platforms to Parachute Chat sessions. Each connector manages its own session lifecycle, trust levels, and message formatting.
Architecture
┌──────────────┐ ┌──────────────┐
│ Telegram │ │ Discord │
│ Message │ │ Message │
└──────┬───────┘ └──────┬───────┘
│ │
▼ ▼
┌──────────────┐ ┌──────────────┐
│ Telegram │ │ Discord │
│ Connector │ │ Connector │
│ │ │ │
│ - Streaming │ │ - Slash cmds │
│ - Ack emoji │ │ - Ack emoji │
│ - Voice │ │ - Typing │
│ - /new /init │ │ - /new /chat │
└──────┬───────┘ └──────┬───────┘
│ │
└────────┬───────────┘
│
┌────────▼────────┐
│ BotConnector │
│ (base.py) │
│ │
│ - Per-chat lock │
│ - Trust levels │
│ - User pairing │
│ - Init flow │
│ - Group history │
│ - Split response│
└────────┬────────┘
│
┌────────▼────────┐
│ Orchestrator │
│ (streaming) │
└─────────────────┘
Source Files
| File | Purpose |
|---|---|
connectors/base.py |
Abstract BotConnector base class, GroupHistoryBuffer |
connectors/telegram.py |
Telegram connector (streaming, voice, commands) |
connectors/discord_bot.py |
Discord connector (slash commands, channel history) |
connectors/config.py |
Bot configuration from vault/.parachute/bots.yaml |
connectors/message_formatter.py |
Claude markdown to platform format conversion |
api/bots.py |
API endpoints for bot management + pairing |
Configuration
Bot connectors are configured in vault/.parachute/bots.yaml:
telegram:
enabled: true
bot_token: "123456:ABC-DEF..."
allowed_users:
- 12345678 # Telegram user IDs
dm_trust_level: vault
group_trust_level: sandboxed
group_mention_mode: mention_only
ack_emoji: "👀"
discord:
enabled: true
bot_token: "MTIzNDU2..."
allowed_users:
- "987654321" # Discord user IDs (strings)
dm_trust_level: vault
group_trust_level: sandboxed
group_mention_mode: mention_only
ack_emoji: "👀"
Trust Levels per Platform
| Setting | Default | Description |
|---|---|---|
dm_trust_level |
vault |
Trust level for DM conversations |
group_trust_level |
sandboxed |
Trust level for group conversations |
group_mention_mode |
mention_only |
Default: only respond when @mentioned in groups |
ack_emoji |
👀 |
Reaction added on message receipt (instant feedback) |
Features
Ack Reactions
Instantly adds an emoji reaction when a message is received. Removed after the response completes. Provides immediate feedback before the AI generates a response.
Streaming (Telegram)
Telegram connector edits a draft message as tokens stream in. Updates every ~30 tokens for real-time output. Falls back to batch send on edit failures.
Group History
Injects recent group messages as context when the bot is mentioned. Uses XML tags (<group_context>) with sanitized display names to resist prompt injection.
User Pairing
Unknown users get a pairing request sent to the Parachute app for approval. Until approved, the bot responds with a "request pending" message.
Per-Chat Locks
Each chat gets an asyncio lock to prevent concurrent message processing. Keyed on stable chat_id (not session_id which changes during finalization).
Voice Messages
Telegram supports voice messages: downloads OGG, converts to WAV (ffmpeg), transcribes with Whisper API, then processes as text.
Session Lifecycle
User sends message
│
▼
┌───────────────────┐
│ is_user_allowed? │──── No ──▶ handle_unknown_user()
└───────┬───────────┘ → Create pairing_request
│ Yes → Create pending session
▼ → "Request sent to owner"
┌───────────────────┐
│ get_or_create_ │
│ session() │
│ │
│ Find by bot link │
│ or create new │
└───────┬───────────┘
│
▼
┌───────────────────┐
│ is_session_ │──── No ──▶ Nudge: "Configure in app"
│ initialized? │ (max 2 nudges, then silent)
└───────┬───────────┘
│ Yes
▼
┌───────────────────┐
│ Check response │
│ mode (per-session)│──── mention_only + no mention ──▶ ignore
└───────┬───────────┘
│
▼
┌───────────────────┐
│ Add ack reaction │
│ Inject group │
│ history if group │
│ Acquire chat lock │
│ Route to │
│ orchestrator │
│ Stream/send │
│ Remove ack │
└───────────────────┘
Telegram Connector
Commands
| Command | Description |
|---|---|
/new |
Archive current session, start fresh |
/init |
Re-initialize session (archive + create pending) |
Streaming Implementation
Telegram streams responses by editing a "draft" message as tokens arrive:
- Send initial empty message (or first chunk)
- Accumulate tokens from orchestrator SSE events
- Every ~30 tokens, edit the message with accumulated text
- Format with
claude_to_telegram()(markdown adjustments for Telegram HTML) - On completion, send final edit with full response
Group Mention Detection
Uses Telegram's entity system for reliable mention detection (not string matching). Checks MessageEntity.MENTION and MessageEntity.TEXT_MENTION types against the bot's username.
Discord Connector
Slash Commands
| Command | Description |
|---|---|
/chat [message] |
Chat with Parachute (deferred response) |
/new |
Start a new conversation (archive current) |
/journal [entry] |
Create a Daily journal entry |
Response Handling
Discord doesn't stream mid-message edits like Telegram. Instead:
- Show typing indicator while processing
- Collect full response from orchestrator
- Format with
claude_to_discord() - Split at 2000-char limit (Discord max), send as reply chain
Group History
Discord has channel.history() API, so history is fetched on-demand rather than buffered. Same XML tag injection and name sanitization as Telegram.
Message Formatting
message_formatter.py converts Claude's markdown output to platform-compatible formats:
| Element | Telegram | Discord |
|---|---|---|
| Bold | <b>text</b> |
**text** |
| Italic | <i>text</i> |
*text* |
| Code | <code>text</code> |
`text` |
| Code block | <pre>text</pre> |
```text``` |
| Max length | 4096 chars | 2000 chars |
Long responses are split at paragraph boundaries using BotConnector.split_response(), preserving code blocks across chunks.
User Pairing Flow
When an unknown user messages the bot:
- Bot creates a
pairing_requestin the database - Bot creates a "pending" session visible in the Parachute app chat list
- Bot replies: "Your request has been sent to the owner"
- Owner sees request in app → approves with trust level
POST /api/bots/pairing/{id}/approvetriggers approval- Connector adds user to allowed list + sends "You've been approved!" message
- Subsequent messages from user are processed normally
Per-user trust level overrides are cached in-memory and looked up from the approved pairing request.
Security Considerations
- Allowlist enforcement: Only
allowed_usersfrom bots.yaml can trigger AI responses - Prompt injection resistance: Group messages are wrapped in
<group_context>XML tags; display names are sanitized (brackets, angle brackets, newlines stripped, max 50 chars) - Trust isolation: Group chats default to
sandboxed(Docker container). DMs default tovault. - Per-chat locks: Prevent race conditions from rapid messages in the same chat
- Bot tokens: Stored in
bots.yamlwhich should be protected (not version-controlled)
Management API
Connectors can be managed through the API or the Parachute app Settings screen:
# Check status
GET /api/bots/status
# Start/stop connectors
POST /api/bots/telegram/start
POST /api/bots/telegram/stop
# View and manage pairing requests
GET /api/bots/pairing
POST /api/bots/pairing/{id}/approve {"trust_level": "vault"}
POST /api/bots/pairing/{id}/deny
# Update configuration
GET /api/bots/config
PUT /api/bots/config {...}
Connectors auto-start on server boot if enabled: true in bots.yaml. Errors during auto-start are logged but never crash the server.