Telegram Bot
This guide walks through connecting your Kraken instance to Telegram — another channel for your personal assistant. Each Telegram user gets their own persistent session with full memory, identity, and self-improving skills. Same Kraken, same brain, just another way to reach it.
Prerequisites
- A running Kraken Agent instance (see Quick Start)
- A Telegram bot token (from @BotFather)
- Python 3.11+
python-telegram-botandkraken-agentpackages
Setup
Create the bot on Telegram
- Open Telegram and message @BotFather
- Send
/newbotand follow the prompts (choose a name and username) - Copy the HTTP API token BotFather gives you
- Optionally, send
/setcommandsto register the bot's commands:start - Start chatting memory - Search the knowledge graph skills - List learned skills soul - View the agent's personality
Install dependencies
pip install python-telegram-bot kraken-agent python-dotenv
Environment variables
Create a .env file:
TELEGRAM_TOKEN=your-telegram-bot-token
KRAKEN_API_URL=http://localhost:8080
KRAKEN_API_KEY=your-kraken-api-key
KRAKEN_MODEL=gpt-5.4
The Bot
"""Telegram bot powered by Kraken Agent."""
import asyncio
import logging
import os
from dotenv import load_dotenv
from telegram import Update
from telegram.ext import (
Application,
CommandHandler,
ContextTypes,
MessageHandler,
filters,
)
from kraken import KrakenClient
load_dotenv()
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)
# --- Kraken Client ---
kraken = KrakenClient(
api_url=os.environ["KRAKEN_API_URL"],
model=os.environ.get("KRAKEN_MODEL", "gpt-5.4"),
api_key=os.environ.get("KRAKEN_API_KEY"),
)
# Telegram message limit
TG_MAX_LENGTH = 4096
def split_message(text: str, limit: int = TG_MAX_LENGTH) -> list[str]:
"""Split a long message into chunks that fit within Telegram's limit."""
if len(text) <= limit:
return [text]
chunks: list[str] = []
remaining = text
while remaining:
if len(remaining) <= limit:
chunks.append(remaining)
break
cut = remaining.rfind("\n\n", 0, limit)
if cut == -1:
cut = remaining.rfind("\n", 0, limit)
if cut == -1:
cut = remaining.rfind(" ", 0, limit)
if cut == -1:
cut = limit
chunks.append(remaining[:cut].rstrip())
remaining = remaining[cut:].lstrip("\n")
return chunks
async def start_command(update: Update, context: ContextTypes.DEFAULT_TYPE) -> None:
"""Handle /start — introduce the bot."""
await update.message.reply_text(
"Hey! I'm your Kraken assistant. Just send me a message and I'll "
"remember everything across our conversations."
)
async def memory_command(update: Update, context: ContextTypes.DEFAULT_TYPE) -> None:
"""Handle /memory <query> — search the knowledge graph."""
query = " ".join(context.args) if context.args else ""
if not query:
await update.message.reply_text(
"Usage: /memory <query>\nExample: /memory What do you know about my projects?"
)
return
result = await asyncio.to_thread(kraken.memory.query, query)
entities = "\n".join(f"• {e.name} ({e.type})" for e in result.entities[:10])
await update.message.reply_text(
f"🔍 Memory results for: {query}\n\n{entities or 'No entities found.'}"
)
async def skills_command(update: Update, context: ContextTypes.DEFAULT_TYPE) -> None:
"""Handle /skills — list learned skills."""
skills = await asyncio.to_thread(kraken.skills.list)
listing = "\n".join(f"• {s.name} v{s.version}" for s in skills[:15])
await update.message.reply_text(
f"⚡ Skills ({len(skills)} total):\n\n{listing or 'No skills yet.'}"
)
async def soul_command(update: Update, context: ContextTypes.DEFAULT_TYPE) -> None:
"""Handle /soul — view the agent's personality."""
soul = await asyncio.to_thread(kraken.identity.get_soul)
await update.message.reply_text(soul.content[:3900])
async def handle_message(update: Update, context: ContextTypes.DEFAULT_TYPE) -> None:
"""Handle regular text messages — the main chat loop."""
message = update.message
if not message or not message.text:
return
user = message.from_user
# Show "typing..." indicator
await message.chat.send_action("typing")
# Each Telegram user gets a unique persistent session
session_key = f"telegram-{user.id}"
reply = await asyncio.to_thread(
kraken.chat,
message.text,
session_key=session_key,
session_name=f"Telegram {user.first_name or user.username or user.id}",
metadata={
"provider": "telegram",
"provider_user_id": str(user.id),
"chat_id": str(message.chat_id),
"username": user.username or "",
},
)
chunks = split_message(reply.content)
for chunk in chunks:
await message.reply_text(chunk)
def main() -> None:
"""Start the bot."""
app = Application.builder().token(os.environ["TELEGRAM_TOKEN"]).build()
app.add_handler(CommandHandler("start", start_command))
app.add_handler(CommandHandler("memory", memory_command))
app.add_handler(CommandHandler("skills", skills_command))
app.add_handler(CommandHandler("soul", soul_command))
app.add_handler(MessageHandler(filters.TEXT & ~filters.COMMAND, handle_message))
logger.info("Starting Telegram bot...")
app.run_polling(allowed_updates=Update.ALL_TYPES)
if __name__ == "__main__":
main()
Run
python bot.py
Message your bot on Telegram and start chatting.
What's Happening
Each time a Telegram user sends a message:
- The bot routes to a session keyed by
telegram-{user_id} - Kraken loads that user's full conversation history, user model, and relevant memory
- The response is generated with full context
- Background workers extract entities, update the user model, and reflect on skills
Since this is the same Kraken instance you use from Discord, CLI, or anywhere else, knowledge transfers across platforms. Tell your assistant something on Telegram and it remembers when you ask from Discord.
Session Routing Options
Per-user sessions (default)
Each user gets their own persistent session:
session_key = f"telegram-{user.id}"
Per-chat sessions
In group chats, give the whole group a shared session:
session_key = f"telegram-chat-{message.chat_id}"
Per-user, per-chat sessions
Separate session per user per group:
session_key = f"telegram-{message.chat_id}-{user.id}"
Group Chat Support
To have the bot respond in group chats (not just DMs), you have two options:
Respond only when mentioned
async def handle_message(update: Update, context: ContextTypes.DEFAULT_TYPE) -> None:
message = update.message
if not message or not message.text:
return
# In groups, only respond when the bot is mentioned
if message.chat.type != "private":
bot_username = (await context.bot.get_me()).username
if f"@{bot_username}" not in message.text:
return
# Strip the mention from the message
content = message.text.replace(f"@{bot_username}", "").strip()
else:
content = message.text
if not content:
return
# ... rest of handler
Respond to replies
# In groups, respond to mentions or replies to the bot
if message.chat.type != "private":
is_reply_to_bot = (
message.reply_to_message
and message.reply_to_message.from_user
and message.reply_to_message.from_user.id == context.bot.id
)
bot_username = (await context.bot.get_me()).username
is_mention = f"@{bot_username}" in message.text
if not is_reply_to_bot and not is_mention:
return
Identity Linking
Link Telegram users to their accounts on other platforms so Kraken recognizes them everywhere:
kraken.identity.link_identity(
canonical_user_id="alice",
provider="telegram",
provider_user_id=str(user.id),
display_name=user.first_name or user.username,
)
Now when Alice talks on Discord and Telegram, Kraken knows it's the same person.
Next Steps
- Set up Scheduled Tasks to have Kraken send you daily briefings on Telegram
- Connect Discord too — same brain, another channel
- Configure the Identity System to link users across platforms
- Customize the agent's personality via SOUL.md