TnsAI

Telegram

Telegram is Sona's default channel. The adapter long-polls the Bot API, so you don't need a public IP, a tunnel, or a webhook server — Sona pulls updates over an outbound HTTPS connection.

If you ran the Quickstart, you already did most of this. This page covers the same flow in more detail and adds the bits the wizard skips.

Get a bot token

  1. Open Telegram, search for @BotFather, start a chat.
  2. Send /newbot.
  3. Pick a display name — what users will see in the chat header. Anything goes.
  4. Pick a username — must be globally unique and end in _bot (e.g. myname_sona_bot).
  5. BotFather replies with the token. It looks like 123456789:ABC-DEF1234ghIkl-zyx57W2v1u123ew11.

Keep the token secret — anyone with it can impersonate your bot.

Optional but useful, still in BotFather:

  • /setdescription — short blurb users see before the first message
  • /setabouttext — long description on the bot's profile page
  • /setuserpic — bot avatar
  • /setcommands — populate the slash-command menu (see slash commands below)
  • /setjoingroups and /setprivacy — group behaviour (default: groups disabled, "privacy mode" on so the bot only sees commands and mentions)

Wire it into Sona

Either re-run sona init (which writes the token into ~/.sona/sona.yml for you and verifies it via getMe), or edit the file directly:

# ~/.sona/sona.yml
channels:
  telegram:
    token: ${TELEGRAM_BOT_TOKEN}   # or paste the literal string

The ${VAR} form makes Sona read from the environment at boot — you keep the token out of the config file but still tie it to the channel config. If you'd rather paste the literal token, that works too.

Restart the daemon:

sona stop
sona

On a healthy start you should see:

INFO  c.t.c.t.TelegramAdapter [] - Telegram adapter started — listening for messages

If you see Unauthorized in the logs, the token is wrong or revoked — re-issue via BotFather /token and try again.

First message + pairing

Open t.me/<your_bot_username> and send hi.

Sona doesn't trust strangers. The first message from an unknown sender returns a pairing code:

You: hi
Bot: Hi! I don't recognize you yet. Please share this pairing code
     with my owner to get access: 483921

You are the owner. Approve from another terminal:

sona pair list           # lists pending requests
sona pair approve 483921 # by code
# or
sona pair approve telegram:123456789  # by scoped id

Now send another message — the bot replies through the LLM.

Pairing is per-(channelId, senderId). Approving telegram:123 does not approve cli:123 or slack:123. This is intentional — channels are tenant boundaries, not just routing labels.

Slash commands

Telegram's slash-command menu helps users discover what a bot can do. Set the menu from BotFather:

/setcommands
@your_bot_username
help - What can this bot do?
new - Start a new conversation
stop - Stop the current task

Sona doesn't currently parse slash commands into special behaviours — they flow into the gateway like any other text. If you want a command to do something specific, register a skill whose match() looks for the /help prefix.

Threads, groups, and multi-user chats

By default Telegram bots only see DMs and messages that mention them. To use Sona in a group:

  1. In BotFather: /mybots → pick your bot → Bot SettingsGroup PrivacyTurn off. (This lets the bot see every message in the group, not just @mentions.)
  2. Add the bot to the group.
  3. Anyone in the group can now talk to it — but pairing applies per-user, so you'll need to approve each user that should be allowed to use the LLM budget.

For 1:1 DMs Sona's session boundary is the user's Telegram ID; conversations across days resume from where they left off (until the daemon is restarted with sona uninstall, or you explicitly purge ~/.sona/sessions/).

Media

The adapter accepts inbound photos, documents, and voice notes; they're attached to the UnifiedMessage as Attachment records. What happens next depends on whether your configured LLM is vision-capable:

AttachmentVision LLM (Claude Sonnet, GPT-4o, Gemini Vision)Text-only LLM
PhotoEncoded inline; passed to the LLM as image inputLogged, not sent to the LLM
Document (PDF, txt)If a tool's wired for it (tnsai-tools PDF / text), the agent can ask for content; otherwise loggedSame
Voice noteNot yet wired — tracked in tnsai-tools STT toolkit

For outbound media, the adapter renders UnifiedResponse.attachments as Telegram media — Sona doesn't ship outbound-media skills today, but the SPI supports it.

Privacy + audit

Sona logs every inbound message + the LLM reply to ~/.sona/sona.log (and the per-session JSON in ~/.sona/sessions/). The Telegram chat metadata (user ID, message ID, timestamps) is in the audit log; the LLM's API call to the provider follows the provider's own retention policy.

If you need a redaction filter on the way in — e.g. strip emails or credit-card numbers before they reach the LLM — see the framework's redaction guide. Wiring it through Sona is one extra config block; no code change.

Troubleshooting

SymptomCauseFix
Unauthorized in logsToken wrong / revokedRe-issue via BotFather /token, re-run sona init
Conflict: terminated by other getUpdates requestAnother process is polling the same botStop the other process; only one consumer per token
Bot is added to a group but doesn't respondGroup privacy is onBotFather → bot settings → group privacy → turn off
Message reaches Sona logs but no replyLLM key is invalid, or budget is exhaustedCheck sona logs --grep ERROR -i for the provider's response
First-message pairing code never arrivesNetwork drop right after the inbound messageSend again; pairing is per-sender, you'll get a fresh code
sona pair approve says "no such code"Daemon restarted between code-issue and approveIssue a new one by sending a new message

Removing the bot

To stop using a bot but keep its token alive for future use:

# ~/.sona/sona.yml
channels: {}     # empty map = no adapters at boot

then sona stop && sona. The bot exists on Telegram's side until you /deletebot via BotFather.

On this page