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:

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:

  1. Send initial empty message (or first chunk)
  2. Accumulate tokens from orchestrator SSE events
  3. Every ~30 tokens, edit the message with accumulated text
  4. Format with claude_to_telegram() (markdown adjustments for Telegram HTML)
  5. 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:

  1. Show typing indicator while processing
  2. Collect full response from orchestrator
  3. Format with claude_to_discord()
  4. 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:

  1. Bot creates a pairing_request in the database
  2. Bot creates a "pending" session visible in the Parachute app chat list
  3. Bot replies: "Your request has been sent to the owner"
  4. Owner sees request in app → approves with trust level
  5. POST /api/bots/pairing/{id}/approve triggers approval
  6. Connector adds user to allowed list + sends "You've been approved!" message
  7. 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_users from 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 to vault.
  • Per-chat locks: Prevent race conditions from rapid messages in the same chat
  • Bot tokens: Stored in bots.yaml which should be protected (not version-controlled)

Management API

Connectors can be managed through the API or the Parachute app Settings screen:

Bot Management
# 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.

Next Steps