A lot of small teams run on a Telegram group. Release dates, who owns what, which bug is fixed in which branch: it is all in there, and none of it is searchable in a useful way a week later.
For Mergetober I built a Telegram connector for cognee, so an agent can answer questions from what a bot has seen in a group or channel. It was my first merged connector.
- Pull request (merged): topoteretes/cognee-community#355
- Issue: topoteretes/cognee#4730
Setting it up
Create a bot with @BotFather, add it to the group or channel, and pass its token to the connector:
import cognee
from cognee_community_connector_telegram import telegram_source
await cognee.remember(
telegram_source(token=bot_token, chats=["@my_team_channel"]),
dataset_name="telegram_chats",
primary_key="id",
write_disposition="merge",
max_rows_per_table=0,
)
answers = await cognee.recall(
"What has been discussed in these chats, and who said what?",
datasets=["telegram_chats"],
)
On startup the connector calls getMe once. Besides checking the token, that tells it whether privacy mode is on, which hides most group messages from a bot, so it warns you instead of silently syncing nothing.
What cognee answered
I sent three messages in a test group and one post in a test channel, then ran the example with OpenAI as the LLM:
Telegram: 4 message row(s), 0 deletion(s).
RememberResult(status='completed', dataset='telegram_chats', items=4, elapsed=30.8s)
Oswin said the group decided to ship v0.3 on Thursday, that Priya is handling the release notes, and that the login bug is fixed in the hotfix branch.
Cognee Test Channel posted that the v0.3 release is planned for Thursday.
The part that took the longest: not losing messages
The Bot API has one way to read messages, getUpdates, and it has a catch: asking for the next page confirms the previous one, and Telegram then drops it for good. A connector that reads two pages in one run, and then fails to load the second, has lost the first.
So the connector reads one page per run (up to 100 updates). The page is only confirmed by the next run, after the load succeeded, and the cursor is written after the last row. The tests drive this hard: a failed load with 150 updates pending, the host revoking the sync between rows with 250 pending, a failure halfway through, and a quiet week after which Telegram may restart its update_id counter. In every case, nothing is lost.
What it does with edits and deletes
-
Edits:
edited_messageandedited_channel_postreplace the same row, keyed bychat_id:message_id. -
A bot removed from a chat, or the chat deleted: every stored message of that chat gets a
_deletedtombstone, and cognee's orphan cleanup removes them from the graph. - A group upgraded to a supergroup: the new chat id keeps syncing, and removal forgets messages stored under both ids.
One thing I checked before writing any code: a bot cannot see single-message deletes. When I deleted messages in my test group, nothing came through, not even a gap in update_id. So deletes work per chat. I raised that on the issue before starting, and the README lists it with the other Bot API limits.
Live run
On a real bot in a test group and a test channel, through a real dlt merge:
# 1. two messages in a group + one channel post
Telegram: 3 message row(s), 0 deletion(s).
# 2. edited message 2 (same key, text replaced, still 3 rows)
Telegram: 1 message row(s), 0 deletion(s).
# 3. nothing new
Telegram: 0 message row(s), 0 deletion(s).
# 4. removed the bot from the group (the channel is untouched)
Telegram: bot removed from Cognee Test Group 2; forgetting its messages.
Telegram: 0 message row(s), 2 deletion(s).
What I learned from the review
The review took a few rounds, and it made the connector better. Several bots in one chat see the same message keys, so each bot now writes its own table and sync state. The host can stop a sync at any point through check_active without losing updates. And I found that cognee 1.4 skipped orphan cleanup once a table became empty, so the last chat's messages stayed in memory after the bot left; the connector requires 1.6.3, which handles it.
The connector is in packages/connector/telegram, with the setup steps in its README and the script I used for this post in examples/example.py.
Top comments (0)