Skip to main content
This page documents the intended user-facing behavior for a Telegram distribution. The integration, trigger configuration, and framework helpers described here are design targets and work in progress rather than shipped functionality.
Telegram distributions connect bot chats, group mentions, replies, and other Telegram activity to Aion agent workflows. The goal is to let a developer configure which inbound Telegram events should count as triggers, normalize them into a generic A2A transport model, and then reply back into the same Telegram chat context by default.

Overview

The default Telegram loop should feel simple even though the transport model is generic:
  1. Configure the Telegram trigger modes at the distribution layer.
  2. Convert each selected Telegram update into a normalized A2A request.
  3. Let the framework adapter resolve the response by normal precedence.
  4. Send the response back into the same Telegram chat, thread, or reply chain by default.
This means the transport contract stays shared across distributions while the configuration layer remains platform-aware. For the lower-level contract, see Distribution and Event.

Default Request Loop

Configuration

WIP.

Message Mapping

Telegram distributions should 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 Outbound

Features

Mentions

Mentions in Telegram groups should be treated as normal inbound message events. The framework sees text plus normalized transport context, and the response flows back into the same group context.
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 should reply back into the same Telegram chat and keep the same reply context when one exists.

Reactions

Telegram reactions should usually arrive as activity events, not as conversational messages. They should still be available to the framework through the same generic envelope.
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.activity.1.0.0":
        return {"messages": [AIMessage(content="Reaction observed.")]}

    return {}

Cards

Telegram does not use the same visual model as Slack Block Kit, but it still benefits from a generic card document. The distribution should interpret that document into Telegram-native layouts such as inline keyboards, media captions, or structured reply content.
from a2a.types import Message, Part, Role
from aion.shared.types import A2AOutbox


def send_telegram_card(state: AgentState) -> dict:
    card_document = """
    <Card title="Digest ready">
      <Text>Choose how you want to continue.</Text>
      <Actions>
        <Button url="https://example.com/digest/42">Open digest</Button>
      </Actions>
    </Card>
    """.strip()

    return {
        "a2a_outbox": A2AOutbox(
            message=Message(
                role=Role.ROLE_AGENT,
                parts=[
                    Part(text="Your digest is ready"),
                    Part(
                        raw=card_document,
                        filename="digest-ready.card.jsx",
                        mediaType="application/vnd.aion.card+jsx",
                    ),
                ],
            )
        )
    }

Streaming

Telegram should preserve the same target message context while streaming. The intent is to post the reply once and then edit that message as new text arrives, similar to how streaming should work in other conversational distributions.
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

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.
def on_telegram_dm(state: AgentState) -> dict:
    inbound = state["messages"][-1]
    return {
        "messages": [
            AIMessage(content=f"Telegram DM received: {inbound.content}")
        ]
    }