> ## Documentation Index
> Fetch the complete documentation index at: https://docs.aion.to/llms.txt
> Use this file to discover all available pages before exploring further.

# Telegram Bot

> Connect a Telegram bot to a Distribution Ion for messages, commands, reactions, and streamed replies.

<Note>
  The core private-message and reply loop is provider-validated. Group mentions, commands, reactions, and streaming are
  implemented in preview while their remaining provider validation is completed. Inbound media and Telegram `voice`
  transcription are supported independently of those preview features.
</Note>

A **Telegram Bot Distribution Ion** connects one Telegram bot to an
[Ion Sequence](/docs/concepts/ions#ions-and-sequences). Private-chat messages, group messages, commands, replies, and
reactions become shared A2A messaging events. Agent output returns to the same Telegram chat and topic by default.

This distribution is for Telegram bots. It does not connect or operate as a person's Telegram account.

## Overview

The default Telegram Bot loop keeps platform behavior at the Distribution boundary:

1. Telegram sends a supported update to the connected bot.
2. The Distribution verifies and converts the update into a normalized A2A request.
3. The Sequence handles the request and returns a response.
4. The Distribution sends the response back into the same Telegram chat, topic, or reply chain.

This means the transport contract stays shared across distributions while the configuration layer
remains platform-aware.

For the lower-level contract, see
[Distribution](/a2a/extensions/aion/distribution/1.0.0) and
[Event](/a2a/extensions/aion/event/1.0.0).

## Default Request Loop

```mermaid theme={null}
sequenceDiagram
participant User as Telegram User
participant Telegram as Telegram
participant Distribution as Aion Telegram Bot Distribution
participant Server as Aion Agent Server
participant Agent as Agent Framework

User->>Telegram: Start bot, then send text, voice, media, reaction, or reply
Telegram->>Distribution: Deliver matching update
Distribution->>Distribution: Verify and normalize supported update
Distribution->>Server: SendMessage with normalized Telegram context
Server->>Agent: Map request into framework state
Agent-->>Server: Return buffered output, explicit outbox, or fallback text response
Server-->>Distribution: Final A2A Message or Task
Distribution-->>Telegram: Reply in the same chat or thread context
```

## Configuration

### Before you begin

You need:

* an Aion organization and a Project containing a Telegram Bot Distribution Ion;
* permission to add integrations to that organization; and
* a Telegram account that can create a bot or supply an existing bot token.

### Create an Aion-managed bot

This is the recommended setup path.

1. Open **Integrations**, select **Telegram Bot**, then add a Telegram Bot account.
2. Choose **Create an Aion-Managed Bot**.
3. Enter the bot display name and optional profile details, then select **Continue**.
4. Review and accept the ownership and management disclosures.
5. Select **Open Telegram** and confirm bot creation in the manager chat.
6. Return to Aion and wait for the Telegram Bot identity to become ready.

The Telegram user remains the bot owner. Aion receives management access needed to configure and operate that bot.
Releasing the identity stops Aion routing, but it does not detach Aion's manager relationship because Telegram does
not provide that operation.

### Connect your own bot

Use this path for a bot you created separately with
[@BotFather](https://core.telegram.org/bots/features#botfather).

1. Create the bot in Telegram and copy its Bot API token.
2. Open **Integrations**, select **Telegram Bot**, then add a Telegram Bot account.
3. Choose **Connect Your Own Bot** and submit the token through the one-time credential form.
4. Wait for Aion to validate the bot and show the Telegram Bot identity as ready.

Treat the token as a secret. Aion does not display it again after submission.

### Assign the bot to a Distribution

Connecting a bot does not automatically bind it to a Project or Distribution.

1. Open the Project in [Composer](/docs/composer).
2. Select the Telegram Bot Distribution Ion and open its **Identity** tab.
3. Select the connected bot under **Telegram Bot**.
4. To make this endpoint part of an Aion, select its **Principal Identity**.
5. Sync the Project changes.

Telegram Bot v1 has no Distribution-specific behavior fields. Telegram privacy mode, group administration, and bot
settings still determine which updates Telegram sends. Aion-managed setup does not change those settings automatically.

### Activate the Distribution

After selecting a ready Telegram Bot identity, turn on **Activate distribution** and sync the Project. Activation
registers the bot's webhook and enables message delivery when reconciliation succeeds.

## Message Mapping

Telegram Bot distributions map inbound and outbound messages through the
same shared transport contracts used by other messaging integrations, while
still preserving Telegram-specific chat and reply context.

**Inbound**

* Protocol-level request metadata and event identity are defined by
  [Distribution](/a2a/extensions/aion/distribution/1.0.0),
  [Event](/a2a/extensions/aion/event/1.0.0), and
  [Distribution/Messaging](/a2a/extensions/aion/distribution/messaging/1.0.0).
* Framework-level request mapping is described in
  [LangGraph Message Mapping](/sdk/langgraph/message-mapping) and
  [Google ADK Message Mapping](/sdk/google-adk/message-mapping).
* Supported `voice`, `audio`, and `document` messages use the shared
  [media and attachments](/docs/distributions/messaging/media-and-attachments) model. Caption text, generated
  transcript text, the temporary Aion file, normalized event data, media state, and the source update remain separate
  ordered parts.

**Outbound**

* Default response precedence is: SDK-managed response buffer first, explicit
  `a2a_outbox` second, and framework-native fallback third.
* Structured outbound messages, reactions, and streaming deltas are defined by
  [Distribution/Messaging](/a2a/extensions/aion/distribution/messaging/1.0.0).
* Framework-level response mapping is described in
  [LangGraph Message Mapping](/sdk/langgraph/message-mapping)
  and
  [Google ADK Message Mapping](/sdk/google-adk/message-mapping).

## Features

### Voice messages

Telegram's provider-designated `voice` message is eligible for automatic transcription. A forwarded or bot-created
voice message receives the same treatment when Telegram still represents it as `voice`. Ordinary `audio` and
`document` attachments are not transcribed automatically.

Aion directly supports Ogg/Opus, MP3, and M4A voice content for recorded speech-to-text. It does not transcode an
unsupported format. The file still reaches the agent with
`transcription.status = "unsupported"` and
`safeFailureCode = "audio_format_not_supported_for_transcription"`.
Other terminal STT failures likewise preserve the file and use a safe failure code without provider diagnostics. A
`no-speech` result does not dispatch an agent request because it has neither provider caption nor usable transcript.

When both are present, the provider-authored caption is the first text part and the generated transcript is the
second. Only the caption and its Telegram entities can select a command; generated transcript text cannot turn a
voice message into a command. Framework adapters preserve every part instead of flattening caption and transcript
provenance.

<Tabs borderBottom>
  <Tab title="LangGraph">
    Inspect every part in `runtime.context.inbox.message.parts`. Ordinary graph state receives all text parts in
    order, while the inbox preserves the file and the `mediaId` metadata needed to associate the transcript.
  </Tab>

  <Tab title="Google ADK">
    Inspect every part in `ctx.aion_runtime_context.inbox.message.parts`. ADK content conversion does not replace the
    complete inbox, so agents can distinguish provider caption, generated transcript, and file content.
  </Tab>

  <Tab title="A2A">
    ```json theme={null}
    {
      "file": {
        "fileWithUri": "https://api.aion.to/files/019d0000-0000-7000-8000-000000000001/versions/019d0000-0000-7000-8000-000000000002/content?grant=example",
        "name": "voice-4002.ogg",
        "mediaType": "audio/ogg"
      },
      "metadata": {
        "https://docs.aion.to/a2a/extensions/aion/event/1.0.0": {
          "schema": "https://docs.aion.to/a2a/extensions/aion/distribution/messaging/1.0.0#MessageMediaPayload",
          "mediaId": "2b20168a-eb23-3ffd-a0aa-e4d730609349",
          "fileId": "019d0000-0000-7000-8000-000000000001",
          "fileVersionId": "019d0000-0000-7000-8000-000000000002",
          "kind": "voice-recording",
          "declaredMediaType": "audio/ogg",
          "byteSize": 4096,
          "durationMs": 2300,
          "availability": {
            "status": "available",
            "refreshable": true,
            "accessExpiresAt": "2026-08-29T19:00:00Z",
            "retentionExpiresAt": "2026-08-29T19:30:00Z"
          },
          "transcription": {
            "status": "completed",
            "language": "en"
          }
        }
      }
    }
    ```
  </Tab>
</Tabs>

The file part's `MessageMediaPayload` metadata carries `fileId`, the stable File Recording identifier, and
`fileVersionId`, the exact immutable File Recordable identifier. Its URL and access and retention deadlines remain
unchanged in task history. Supported Telegram media bytes are retained for one hour after materialization. The access
grant may expire sooner; after byte retention ends, the historical URL returns `410 Gone` without rewriting the task.

Telegram's standard hosted Bot API download limit is 20 MB. Aion does not apply a separate voice-duration limit, but
download and STT work remain bounded by a size-aware processing deadline.

### Attachments

Telegram `audio` and `document` messages receive normalized metadata and a temporary Aion file part. Aion downloads
the content without exposing the bot token, validates bounded size and MIME/signature information, and streams the
same ingress toward storage and any eligible STT consumer. Only `voice` is eligible for automatic transcription.

The Aion file URL is authenticated and independent of Telegram's credential-bearing download URL. Its one-hour byte
retention and immutable task-history behavior match voice files. Outbound media delivery is not supported in this
release; agent responses remain text, reactions, or supported structured messaging actions.

### Mentions

Mentions in Telegram groups are treated as normal inbound message events. The framework sees
text plus normalized transport context, and the response flows back into the same group context.
[Bot API 10.0](https://core.telegram.org/bots/api-changelog#may-8-2026) permits certain bot-authored
messages in groups. Telegram Bot v1 intentionally ignores bot-authored input; bot-to-bot triggering is
not supported.

<Tabs borderBottom>
  <Tab title="LangGraph">
    ```python theme={null}
    from typing import Annotated, Optional, TypedDict
    from langchain_core.messages import AIMessage, BaseMessage
    from langgraph.graph import add_messages

    from aion.shared.types import A2AInbox


    class AgentState(TypedDict):
        messages: Annotated[list[BaseMessage], add_messages]
        a2a_inbox: Optional[A2AInbox]


    def on_telegram_mention(state: AgentState) -> dict:
        inbound = state["messages"][-1]
        return {
            "messages": [
                AIMessage(content=f"Telegram mention received: {inbound.content}")
            ]
        }
    ```

    The Distribution replies in the same Telegram chat and keeps the same reply context
    when one exists.
  </Tab>

  <Tab title="Google ADK">
    ```python theme={null}
    from google.adk.agents import BaseAgent
    from google.adk.events import Event


    class TelegramMentionAgent(BaseAgent):
        async def _run_async_impl(self, ctx):
            texts = [
                part.text
                for part in ctx.a2a_inbox.message.parts
                if getattr(part, "text", None)
            ]
            text = "\n".join(texts)
            yield Event(
                author=self.name,
                content={"parts": [{"text": f"Telegram mention received: {text}"}]},
            )
    ```
  </Tab>

  <Tab title="A2A">
    ```json theme={null}
    {
      "message": {
        "role": "ROLE_USER",
        "extensions": [
          "https://docs.aion.to/a2a/extensions/aion/distribution/1.0.0",
          "https://docs.aion.to/a2a/extensions/aion/event/1.0.0"
        ],
        "parts": [
          { "text": "@bot summarize the last three messages" },
          {
            "data": {
              "userId": "tg-user-42",
              "messageId": "4002",
              "contextId": "tg-chat-9988",
              "parentContextId": "3998",
              "trajectory": "conversation"
            },
            "mediaType": "application/json",
            "metadata": {
              "https://docs.aion.to/a2a/extensions/aion/event/1.0.0": {
                "schema": "https://docs.aion.to/a2a/extensions/aion/distribution/messaging/1.0.0#MessageEventPayload"
              }
            }
          },
          {
            "data": {
              "provider": "telegram",
              "event": {
                "update_id": 9001,
                "message": {
                  "message_id": 4002,
                  "chat": { "id": -9988, "type": "supergroup" },
                  "from": { "id": 42, "username": "tg_user_42" },
                  "text": "@bot summarize the last three messages"
                }
              }
            },
            "mediaType": "application/json",
            "metadata": {
              "https://docs.aion.to/a2a/extensions/aion/event/1.0.0": {
                "schema": "https://docs.aion.to/a2a/extensions/aion/distribution/messaging/1.0.0#SourceSystemEventPayload"
              }
            }
          }
        ],
        "metadata": {
          "https://docs.aion.to/a2a/extensions/aion/event/1.0.0": {
            "type": "to.aion.distribution.message.1.0.0",
            "source": "aion://distribution/telegram-dist-123",
            "id": "evt-telegram-mention-001"
          }
        }
      }
    }
    ```
  </Tab>
</Tabs>

### Commands

A leading Telegram `bot_command` entity is normalized as a command event. Commands may include an
optional `@botusername`; the distribution accepts that form only when it names the connected bot.
The raw argument tail and complete Telegram update remain available to the agent.

<Tabs borderBottom>
  <Tab title="LangGraph">
    ```python theme={null}
    def on_telegram_command(state: AgentState) -> dict:
        inbound = state["messages"][-1]
        return {
            "messages": [
                AIMessage(content=f"Telegram command received: {inbound.content}")
            ]
        }
    ```
  </Tab>

  <Tab title="Google ADK">
    ```python theme={null}
    class TelegramCommandAgent(BaseAgent):
        async def _run_async_impl(self, ctx):
            texts = [
                part.text
                for part in ctx.a2a_inbox.message.parts
                if getattr(part, "text", None)
            ]
            text = "\n".join(texts)
            yield Event(
                author=self.name,
                content={"parts": [{"text": f"Telegram command received: {text}"}]},
            )
    ```
  </Tab>

  <Tab title="A2A">
    ```json theme={null}
    {
      "data": {
        "userId": "tg-user-42",
        "contextId": "tg-chat-9988",
        "command": "/summarize",
        "arguments": "last 20 messages"
      },
      "mediaType": "application/json",
      "metadata": {
        "https://docs.aion.to/a2a/extensions/aion/event/1.0.0": {
          "schema": "https://docs.aion.to/a2a/extensions/aion/distribution/messaging/1.0.0#CommandEventPayload"
        }
      }
    }
    ```
  </Tab>
</Tabs>

### Reactions

Telegram delivers actor-specific reaction changes as `message_reaction` updates. In groups and
supergroups, the bot must be an administrator and the webhook must explicitly request
`message_reaction`. Anonymous aggregate `message_reaction_count` updates do not identify an actor
and are not normalized into Aion reaction events.

Each added or removed reaction becomes
[ReactionEventPayload](/a2a/extensions/aion/distribution/messaging/1.0.0#reactioneventpayload) with
the original Telegram update preserved alongside it.

<Tabs borderBottom>
  <Tab title="LangGraph">
    ```python theme={null}
    def on_telegram_reaction(state: AgentState) -> dict:
        inbox = state.get("a2a_inbox")
        event_type = inbox.message.metadata[
            "https://docs.aion.to/a2a/extensions/aion/event/1.0.0"
        ]["type"]

        if event_type == "to.aion.distribution.reaction.1.0.0":
            return {"messages": [AIMessage(content="Reaction observed.")]}

        return {}
    ```
  </Tab>

  <Tab title="Google ADK">
    ```python theme={null}
    class TelegramReactionAgent(BaseAgent):
        async def _run_async_impl(self, ctx):
            yield Event(
                author=self.name,
                content={"parts": [{"text": "Reaction observed."}]},
            )
    ```
  </Tab>

  <Tab title="A2A">
    ```json theme={null}
    {
      "message": {
        "role": "ROLE_USER",
        "extensions": [
          "https://docs.aion.to/a2a/extensions/aion/distribution/messaging/1.0.0"
        ],
        "metadata": {
          "https://docs.aion.to/a2a/extensions/aion/event/1.0.0": {
            "type": "to.aion.distribution.reaction.1.0.0",
            "source": "aion://distribution/telegram-dist-123",
            "id": "evt-telegram-reaction-001"
          }
        },
        "parts": [
          {
            "data": {
              "userId": "tg-user-42",
              "contextId": "tg-chat-9988",
              "messageId": "4002",
              "reactionKey": "🔥",
              "displayValue": "🔥",
              "action": "added",
              "isCustom": false
            },
            "mediaType": "application/json",
            "metadata": {
              "https://docs.aion.to/a2a/extensions/aion/event/1.0.0": {
                "schema": "https://docs.aion.to/a2a/extensions/aion/distribution/messaging/1.0.0#ReactionEventPayload"
              }
            }
          },
          {
            "data": {
              "provider": "telegram",
              "event": {
                "update_id": 9002,
                "message_reaction": {
                  "chat": { "id": -9988, "type": "supergroup" },
                  "message_id": 4002,
                  "user": { "id": 42, "username": "tg_user_42" },
                  "old_reaction": [],
                  "new_reaction": [{ "type": "emoji", "emoji": "🔥" }]
                }
              }
            },
            "mediaType": "application/json",
            "metadata": {
              "https://docs.aion.to/a2a/extensions/aion/event/1.0.0": {
                "schema": "https://docs.aion.to/a2a/extensions/aion/distribution/messaging/1.0.0#SourceSystemEventPayload"
              }
            }
          }
        ]
      }
    }
    ```
  </Tab>
</Tabs>

Agents can add or remove one supported non-paid bot reaction by emitting a
[ReactionActionPayload](/a2a/extensions/aion/distribution/messaging/1.0.0#reactionactionpayload)
through the reserved `aion:reaction` artifact. The distribution validates the current chat and
message before calling Telegram.

### Cards

Cards are not planned for the Telegram Bot distribution. It does not interpret Distribution/Cards,
Telegram Rich Messages, inline keyboards, or callback-button actions. Agents return plain
text for Telegram responses.

### Streaming

Telegram preserves one target context for the full response. In an eligible private chat, the
distribution can show coalesced partial text through Telegram message drafts before sending the
final message. In groups and supergroups, it posts once and applies rate-limited text edits.

The Telegram Bot distribution controls the `typing` chat action while it waits for provider-visible
output. No downstream A2A typing event is required, and a `working` task status alone does not
activate typing.

<Tabs borderBottom>
  <Tab title="LangGraph">
    ```python theme={null}
    from langchain_core.messages import AIMessageChunk
    from langgraph.types import StreamWriter
    from aion.langgraph import emit_message


    def stream_telegram_reply(state: AgentState, writer: StreamWriter):
        emit_message(writer, AIMessageChunk(content="Here"))
        emit_message(writer, AIMessageChunk(content=" is the first part "))
        emit_message(writer, AIMessageChunk(content="of the answer."))
        return state
    ```
  </Tab>

  <Tab title="Google ADK">
    ```python theme={null}
    class TelegramStreamingAgent(BaseAgent):
        async def _run_async_impl(self, ctx):
            yield Event(
                author=self.name,
                content={"parts": [{"text": "Here"}]},
                partial=True,
            )
            yield Event(
                author=self.name,
                content={"parts": [{"text": " is the first part "}]},
                partial=True,
            )
            yield Event(
                author=self.name,
                content={"parts": [{"text": "of the answer."}]},
            )
    ```
  </Tab>

  <Tab title="A2A">
    ```json theme={null}
    {
      "artifactUpdate": {
        "taskId": "task-telegram-stream-42",
        "contextId": "ctx-telegram-stream-42",
        "append": true,
        "lastChunk": false,
        "metadata": {
          "https://docs.aion.to/a2a/extensions/aion/distribution/messaging/1.0.0": {
            "schema": "https://docs.aion.to/a2a/extensions/aion/distribution/messaging/1.0.0#StreamDeltaPayload"
          }
        },
        "artifact": {
          "artifactId": "aion:stream-delta",
          "parts": [
            { "text": "Here is the first part " }
          ]
        }
      }
    }
    ```
  </Tab>
</Tabs>

### DMs

Telegram private chats are the cleanest default request loop. The inbound message maps to
`trajectory = "direct-message"` and the default outbound response goes right back to that chat.

> **Telegram Bot limitation:** A bot cannot start a new private conversation. The user must first start
> or message the bot. Aion can reply in that established private-chat context, but an agent cannot
> use an arbitrary Telegram user ID to open a new DM.

<Tabs borderBottom>
  <Tab title="LangGraph">
    ```python theme={null}
    def on_telegram_dm(state: AgentState) -> dict:
        inbound = state["messages"][-1]
        return {
            "messages": [
                AIMessage(content=f"Telegram DM received: {inbound.content}")
            ]
        }
    ```
  </Tab>

  <Tab title="Google ADK">
    ```python theme={null}
    class TelegramDMAgent(BaseAgent):
        async def _run_async_impl(self, ctx):
            texts = [
                part.text
                for part in ctx.a2a_inbox.message.parts
                if getattr(part, "text", None)
            ]
            text = "\n".join(texts)
            yield Event(
                author=self.name,
                content={"parts": [{"text": f"Telegram DM received: {text}"}]},
            )
    ```
  </Tab>

  <Tab title="A2A">
    ```json theme={null}
    {
      "data": {
        "userId": "tg-user-42",
        "messageId": "4002",
        "contextId": "tg-private-chat-42",
        "parentContextId": null,
        "trajectory": "direct-message"
      }
    }
    ```
  </Tab>
</Tabs>
