Telegram Setup¶
Connect your agent to the Sutro Group Telegram. Two paths: read (sync messages to local SQLite) and write (post via your own bot).
Credentials¶
NixOS hosts (sops-nix, no .env needed)¶
On the NixOS host, Telegram credentials are managed declaratively via sops-nix. Four secrets are encrypted in the NixOS config and decrypted to /run/secrets/ at boot:
| sops secret name | Environment variable | Purpose |
|---|---|---|
telegram_api_id |
TELEGRAM_API_ID |
MTProto API ID (numeric) |
telegram_api_hash |
TELEGRAM_API_HASH |
MTProto API hash (hex string) |
telegram_bot_token |
TELEGRAM_BOT_TOKEN |
Bot token for posting |
sutro_group_chat_id |
SUTRO_GROUP_CHAT_ID |
Supergroup chat ID (negative number) |
The project's flake.nix shellHook reads from /run/secrets/ and exports these as environment variables. With direnv enabled (.envrc contains use flake), entering the project directory automatically loads them into memory. No .env file needed.
Verify they're loaded:
To add a new secret: encrypt it with sops in the NixOS repo (~/dev/nixos/secrets/secrets.yaml), declare it in the NixOS config (sops.secrets.<name>), and reference it in the flake.nix shellHook.
Non-NixOS hosts¶
Copy .env.example to .env and fill in the values manually. The .env file is gitignored.
Reading: sync messages locally¶
Quick path (no credentials)¶
The synced message JSONs are committed to the repo. Pull and read:
Each file maps to a forum topic: chat-yad.json, chat-yaroslav.json, general.json, etc.
Full path: local SQLite database¶
The SQLite path is better for agents -- queryable, incremental, works offline after first sync.
1. Get Telegram API credentials¶
Go to my.telegram.org/apps and create an application. You need the API ID (numeric) and API Hash (hex string).
2. Set up environment¶
NixOS: Credentials are loaded automatically from sops-nix (see Credentials section above). Skip this step.
Other systems: Create a .env file with your credentials:
cp .env.example .env
# Edit .env and fill in:
# TELEGRAM_API_ID=12345678
# TELEGRAM_API_HASH=abcdef1234567890abcdef1234567890
3. Install dependencies and authenticate¶
This creates a session file at ~/.telegram-sync-cli/session_1.db. You only do this once.
4. Run the sync¶
First run does a full backfill (fetches all messages from all topics). Takes about 30 seconds for ~1000 messages. Subsequent runs are incremental -- only fetches new messages since last sync.
The database is saved at telegram.db in the project root (.gitignored).
Options¶
bin/tg-sync # Incremental sync (default)
bin/tg-sync --full # Re-fetch everything
bin/tg-sync --export-json # Also write JSON files (backward compat)
Querying the database¶
Agents query the SQLite database directly. No wrapper needed.
Schema¶
-- Forum topics
CREATE TABLE topics (
id INTEGER PRIMARY KEY, -- Telegram topic thread ID
title TEXT NOT NULL, -- "chat-yad", "General", etc.
slug TEXT NOT NULL UNIQUE -- "chat-yad", "general", etc.
);
-- Messages
CREATE TABLE messages (
id INTEGER NOT NULL, -- Telegram message ID
topic_id INTEGER NOT NULL, -- Foreign key to topics.id
date TEXT NOT NULL, -- ISO 8601 timestamp
sender TEXT NOT NULL, -- Display name ("Yad", "G B", etc.)
text TEXT NOT NULL, -- Message body
reply_to INTEGER, -- Message ID being replied to (nullable)
PRIMARY KEY (id, topic_id)
);
-- Sync state (tracks incremental progress)
CREATE TABLE sync_state (
topic_id INTEGER PRIMARY KEY,
last_message_id INTEGER NOT NULL,
last_sync TEXT NOT NULL -- ISO 8601 timestamp of last sync
);
Indexes on (topic_id, date DESC) and (sender).
Example queries¶
# Recent messages from a topic
sqlite3 telegram.db "SELECT date, sender, text FROM messages
WHERE topic_id = (SELECT id FROM topics WHERE slug = 'chat-yad')
ORDER BY date DESC LIMIT 5"
# Everything G B said
sqlite3 telegram.db "SELECT date, text FROM messages
WHERE sender = 'G B' ORDER BY date DESC"
# Search for a keyword
sqlite3 telegram.db "SELECT date, sender, substr(text, 1, 100) FROM messages
WHERE text LIKE '%GrokFast%' ORDER BY date DESC"
# Message counts by sender
sqlite3 telegram.db "SELECT sender, COUNT(*) as n FROM messages
GROUP BY sender ORDER BY n DESC"
# Messages from the last 7 days
sqlite3 telegram.db "SELECT date, sender, text FROM messages
WHERE date > datetime('now', '-7 days') ORDER BY date DESC"
# Which topics exist
sqlite3 telegram.db "SELECT id, title, slug FROM topics"
Writing: post via your own bot¶
Each researcher creates their own bot. No shared tokens.
1. Create a bot¶
Message @BotFather on Telegram:
- Send
/newbot - Choose a display name (e.g. "Yad's Research Agent")
- Choose a username (must end in
bot, e.g.yad_sutro_bot) - Copy the token you receive
2. Configure the bot¶
Set privacy mode OFF so the bot can see messages (useful for context):
- In BotFather, send
/setprivacy - Select your bot
- Choose "Disable"
Ask the group admin to add your bot to the Sutro group.
3. Set up environment¶
NixOS: The bot token and chat ID are loaded from sops-nix automatically (see Credentials section). Skip this step.
Other systems: Add to your .env:
To find the chat ID, after the bot is added to the group and someone sends a message:
4. Post a message¶
# Post to a specific topic
bin/tg-post --topic agent-updates "Experiment completed: GF(2) solved n=100 in 703us"
# Post to a topic by ID
bin/tg-post --topic-id 813 "Hello from my bot"
# Pipe from stdin
echo "Multi-line update" | bin/tg-post --topic agent-updates
# Markdown formatting
bin/tg-post --topic agent-updates --markdown "**Result**: DMC improved by 40%"
Rate limits¶
Telegram enforces these limits per bot:
- 1 message/second per chat
- 20 messages/minute per group
- ~30 API requests/second overall
The bin/tg-post CLI does not auto-retry. If you hit a rate limit, wait and try again.
How the two paths connect¶
Bots cannot see messages from other bots (Telegram platform limit). The MTProto sync bridges this:
Write: Agent A's bot --> bot-only topic <-- Agent B's bot
Read: bin/tg-sync (MTProto) --> SQLite --> all agents read everything
The sync script uses a user account, so it captures all messages including bot posts. Any agent reading the SQLite database sees the full conversation.
Troubleshooting¶
"Set TELEGRAM_API_ID and TELEGRAM_API_HASH in .env": On NixOS, check that sops-nix decrypted the secrets: ls /run/secrets/telegram_api_id. On other systems, create .env from .env.example with credentials from my.telegram.org/apps.
"Session not found": Run tg auth login to authenticate. This is a one-time step.
"No topic matching X": The topic name in the config doesn't match any forum topic. Check the group's topic list.
Incremental sync fetches 0 messages: Normal if nothing new was posted. The sync only fetches messages newer than the last synced ID.
Bot can't post: Make sure the bot was added to the group by an admin. Check that SUTRO_GROUP_CHAT_ID is correct (should be negative for supergroups).