<!-- source: https://deepwiki.com/qhkm/zeptoclaw/7.2-telegram-integration -->

# Telegram Integration

- src/agent/loop.rs
- src/channels/telegram.rs

This page documents the Telegram bot channel implementation in ZeptoClaw. The Telegram channel enables bidirectional communication between users and agents via the Telegram Bot API, using the `teloxide` library for bot operations.

For general channel architecture concepts, see [Channel Architecture](https://github.com/qhkm/zeptoclaw/blob/2c743d31/Channel Architecture) For runtime model switching mechanisms shared across channels, see [Runtime Model Switching](https://github.com/qhkm/zeptoclaw/blob/2c743d31/Runtime Model Switching)

## Overview

The Telegram integration provides:

- Inbound messaging via Telegram Bot API long polling src/channels/telegram.rs 348-402
- Outbound messaging via teloxide::Bot::send_message src/channels/telegram.rs 800-836
- Per-chat model/persona overrides with /model and /persona commands src/channels/telegram.rs 510-667
- Forum topic isolation for multi-conversation bots in Telegram groups src/channels/telegram.rs 467-480
- Photo attachment support with automatic download and conversion src/channels/telegram.rs 704-749
- Allowlist-based access control supporting both numeric user IDs and @usernames src/channels/telegram.rs 133-149
- Persistent override storage via LongTermMemory src/channels/telegram.rs 340-345
- Visual Feedback via automated typing indicators during LLM generation src/channels/telegram.rs 90-104

The implementation is feature-complete and production-ready, with exponential backoff retry on startup failures [src/channels/telegram.rs60-65](https://github.com/qhkm/zeptoclaw/blob/2c743d31/src/channels/telegram.rs#L60-L65) and panic recovery [src/channels/telegram.rs351-354](https://github.com/qhkm/zeptoclaw/blob/2c743d31/src/channels/telegram.rs#L351-L354)

**Sources:** [src/channels/telegram.rs1-37](https://github.com/qhkm/zeptoclaw/blob/2c743d31/src/channels/telegram.rs#L1-L37) [src/channels/telegram.rs60-65](https://github.com/qhkm/zeptoclaw/blob/2c743d31/src/channels/telegram.rs#L60-L65) [src/channels/telegram.rs348-402](https://github.com/qhkm/zeptoclaw/blob/2c743d31/src/channels/telegram.rs#L348-L402)

## Architecture

```

```

**Diagram: TelegramChannel Component Architecture**

The `TelegramChannel` struct [src/channels/telegram.rs164-190](https://github.com/qhkm/zeptoclaw/blob/2c743d31/src/channels/telegram.rs#L164-L190) implements the `Channel` trait and manages:

**Sources:** [src/channels/telegram.rs94-104](https://github.com/qhkm/zeptoclaw/blob/2c743d31/src/channels/telegram.rs#L94-L104) [src/channels/telegram.rs164-190](https://github.com/qhkm/zeptoclaw/blob/2c743d31/src/channels/telegram.rs#L164-L190) [src/channels/telegram.rs192-232](https://github.com/qhkm/zeptoclaw/blob/2c743d31/src/channels/telegram.rs#L192-L232)

## Configuration

The `TelegramConfig` struct defines the channel configuration:

```

```

### Configuration Example

```

```

### Allowlist Matching

The allowlist supports both numeric user IDs and usernames with optional `@` prefix. Matching is case-insensitive [src/channels/telegram.rs133-149](https://github.com/qhkm/zeptoclaw/blob/2c743d31/src/channels/telegram.rs#L133-L149):

**Sources:** [src/channels/telegram.rs133-149](https://github.com/qhkm/zeptoclaw/blob/2c743d31/src/channels/telegram.rs#L133-L149) [src/channels/telegram.rs164-190](https://github.com/qhkm/zeptoclaw/blob/2c743d31/src/channels/telegram.rs#L164-L190)

## Message Flow

```

```

**Diagram: Telegram Message Processing Flow**

### Inbound Path

1. Polling loop started in start() src/channels/telegram.rs 348-402 using teloxide::Dispatcher .
2. Startup check via bot.get_me() with exponential backoff (max 10 attempts) src/channels/telegram.rs 356-401
3. Message handler endpoint receives Update::filter_message() .
4. Allowlist validation src/channels/telegram.rs 429-460 checks user ID and username.
5. Command interception src/channels/telegram.rs 510-667 for /model and /persona .
6. InboundMessage construction src/channels/telegram.rs 669-703 with metadata and media.
7. Bus publication via bus.publish_inbound() src/channels/telegram.rs 751-753

### Outbound Path

1. MessageBus subscription in start() spawns outbound handler src/channels/telegram.rs 772-840
2. HTML rendering via render_telegram_html() src/channels/telegram.rs 107-164 converts Markdown to Telegram-compatible HTML tags (bold, italic, code, spoilers).
3. API call using bot.send_message() with optional parse_mode=Html src/channels/telegram.rs 800-836

**Sources:** [src/channels/telegram.rs107-164](https://github.com/qhkm/zeptoclaw/blob/2c743d31/src/channels/telegram.rs#L107-L164) [src/channels/telegram.rs348-402](https://github.com/qhkm/zeptoclaw/blob/2c743d31/src/channels/telegram.rs#L348-L402) [src/channels/telegram.rs772-840](https://github.com/qhkm/zeptoclaw/blob/2c743d31/src/channels/telegram.rs#L772-L840)

## Forum Topics Support

Telegram forum groups have thread IDs that enable multi-conversation isolation. The channel extracts `thread_id` from `Message::thread_id` [src/channels/telegram.rs467-480](https://github.com/qhkm/zeptoclaw/blob/2c743d31/src/channels/telegram.rs#L467-L480) and uses it for:

### Topic-Aware Session Routing

```

```

**Diagram: Forum Topic Isolation**

The `override_key` format [src/channels/telegram.rs476-480](https://github.com/qhkm/zeptoclaw/blob/2c743d31/src/channels/telegram.rs#L476-L480):

- Without thread : "{chat_id}"
- With thread : "{chat_id}:{thread_id}"

This allows each forum topic to have independent:

- Conversation history (separate session_key ) src/channels/telegram.rs 675-681
- Model override (scoped to override_key ) src/channels/telegram.rs 518-523
- Persona override (scoped to override_key ) src/channels/telegram.rs 596-601

When sending replies, the channel attaches `message_thread_id` using the helper function `apply_thread_id()` [src/channels/telegram.rs489-508](https://github.com/qhkm/zeptoclaw/blob/2c743d31/src/channels/telegram.rs#L489-L508) to ensure responses appear in the correct topic thread.

**Sources:** [src/channels/telegram.rs467-508](https://github.com/qhkm/zeptoclaw/blob/2c743d31/src/channels/telegram.rs#L467-L508) [src/channels/telegram.rs675-681](https://github.com/qhkm/zeptoclaw/blob/2c743d31/src/channels/telegram.rs#L675-L681)

## Runtime Model/Persona Switching

### Command Syntax

### Model Override Flow

The handler [src/channels/telegram.rs513-583](https://github.com/qhkm/zeptoclaw/blob/2c743d31/src/channels/telegram.rs#L513-L583) intercepts `/model` commands before agent processing:

1. Parse command using parse_model_command() from model_switch module src/channels/telegram.rs 514-516
2. Read/write override in model_overrides: ModelOverrideStore (in-memory RwLock<HashMap> ) src/channels/telegram.rs 518-523
3. Persist to LTM via persist_single() src/channels/telegram.rs 539-541
4. Send confirmation back to user via bot.send_message() src/channels/telegram.rs 544-554

The override is attached to `InboundMessage` as metadata [src/channels/telegram.rs683-693](https://github.com/qhkm/zeptoclaw/blob/2c743d31/src/channels/telegram.rs#L683-L693):

```

```

**Sources:** [src/channels/telegram.rs510-667](https://github.com/qhkm/zeptoclaw/blob/2c743d31/src/channels/telegram.rs#L510-L667) [src/channels/telegram.rs683-703](https://github.com/qhkm/zeptoclaw/blob/2c743d31/src/channels/telegram.rs#L683-L703)

## Photo Attachments

The channel supports image messages by downloading the largest photo size and converting it to a `MediaAttachment`:

1. Detect photo via msg.photo() returns Vec<PhotoSize> src/channels/telegram.rs 704
2. Select largest using .last() (Telegram orders by size) src/channels/telegram.rs 705
3. Get file metadata via bot.get_file(largest.file.id) src/channels/telegram.rs 708
4. Build download URL as https://api.telegram.org/file/bot{token}/{file.path} src/channels/telegram.rs 711
5. Download bytes using reqwest::get() src/channels/telegram.rs 713
6. Size check enforces 20MB limit src/channels/telegram.rs 719
7. Create MediaAttachment with MediaType::Image and MIME type image/jpeg src/channels/telegram.rs 730-734
8. Attach to InboundMessage via .with_media(media) src/channels/telegram.rs 747

**Sources:** [src/channels/telegram.rs704-749](https://github.com/qhkm/zeptoclaw/blob/2c743d31/src/channels/telegram.rs#L704-L749)

## Security and Access Control

### Startup Retry with Exponential Backoff

The channel implements robust startup handling [src/channels/telegram.rs351-401](https://github.com/qhkm/zeptoclaw/blob/2c743d31/src/channels/telegram.rs#L351-L401):

### Proxy Support

The bot client is built with `no_proxy()` [src/channels/telegram.rs256-263](https://github.com/qhkm/zeptoclaw/blob/2c743d31/src/channels/telegram.rs#L256-L263) to disable macOS dynamic-store proxy detection, which can crash in sandboxed environments.

### Allowlist Enforcement

Access control is performed by checking the user ID and username against the configured allowlist [src/channels/telegram.rs429-460](https://github.com/qhkm/zeptoclaw/blob/2c743d31/src/channels/telegram.rs#L429-L460) Blocked users receive no response; the event is logged and ignored.

**Sources:** [src/channels/telegram.rs60-65](https://github.com/qhkm/zeptoclaw/blob/2c743d31/src/channels/telegram.rs#L60-L65) [src/channels/telegram.rs256-263](https://github.com/qhkm/zeptoclaw/blob/2c743d31/src/channels/telegram.rs#L256-L263) [src/channels/telegram.rs351-401](https://github.com/qhkm/zeptoclaw/blob/2c743d31/src/channels/telegram.rs#L351-L401) [src/channels/telegram.rs429-460](https://github.com/qhkm/zeptoclaw/blob/2c743d31/src/channels/telegram.rs#L429-L460)

## Implementation Details

### HTML Rendering and Validation

Telegram's HTML mode supports a subset of tags. The channel implements `render_telegram_html` [src/channels/telegram.rs107-164](https://github.com/qhkm/zeptoclaw/blob/2c743d31/src/channels/telegram.rs#L107-L164) which:

- Extracts code regions to placeholders to avoid regex interference src/channels/telegram.rs 118-119
- Converts Markdown headers, bold, italics, and spoilers to HTML src/channels/telegram.rs 121-133
- Validates tag nesting using html_tags_valid() to prevent API errors src/channels/telegram.rs 148-164
- Strips tags via strip_html_tags() if validation fails as a fail-safe src/channels/telegram.rs 167-171

### Typing Indicators

To improve user experience, the channel sends "typing" actions [src/channels/telegram.rs94-104](https://github.com/qhkm/zeptoclaw/blob/2c743d31/src/channels/telegram.rs#L94-L104)

- Automatic start : Triggered when a message is sent to the AgentLoop .
- Concurrency Management : Uses a TypingMap (DashMap) with generation counters to ensure cleanup tasks don't kill newer indicators src/channels/telegram.rs 94-104
- Automatic stop : The indicator is cancelled via a CancellationToken when the outbound response is received or the session times out.

**Sources:** [src/channels/telegram.rs94-104](https://github.com/qhkm/zeptoclaw/blob/2c743d31/src/channels/telegram.rs#L94-L104) [src/channels/telegram.rs107-171](https://github.com/qhkm/zeptoclaw/blob/2c743d31/src/channels/telegram.rs#L107-L171)
