Downcity
ServicesChat Service

Telegram

Chat with your agent and receive notifications on Telegram

Telegram service

⚠️ Simplified mode (2026-02-03): the current downcity package disables approvals (full-permission execution). Mentions of “approval prompts” in older docs may be outdated.

Use Telegram to chat with Downcity (DM or group chat).

Want the internal flow? See: /en/docs/services/chat/telegram-message-flow

Setup

1) Create a Telegram bot

  1. Open Telegram and find @BotFather
  2. Send /newbot
  3. Follow the prompts
  4. Copy the HTTP API Token

2) Create channel account and bind channel

Create a Telegram channel account in Console UI Global / Channel Accounts:

  1. account id: for example telegram-main
  2. channel: telegram
  3. bot token: token from BotFather

Then bind it in project ship.json:

{
  "services": {
    "chat": {
      "channels": {
        "telegram": {
          "enabled": true,
          "channelAccountId": "telegram-main"
        }
      }
    }
  }
}

Downcity uses a tool-strict chat mode: the agent should use the chat_send tool to decide when/how to send replies (multiple/staged messages are allowed).

3) Start Downcity

city agent start

In logs you should see:

  • Starting Telegram Bot...
  • Bot username: @...
  • Telegram Bot started

How to use

Direct message (DM)

Send /start (or any message) to your bot.

Send files (PDF / images / audio / video)

You can send a file directly to the bot (document/photo/voice/audio/video).

  • If you add text while sending the file, Telegram treats it as a caption — Downcity will read that caption as your instruction.
  • Downcity saves the file under .ship/.cache/telegram/ and passes it to the agent as an @attach ... line.

Example: send a PDF with caption: Summarize this PDF and list action items.

Local STT model management

If you want to manage local voice-to-text models, use the dedicated voice extension command group:

# enable and install model
city voice on SenseVoiceSmall

# inspect current voice config
city voice status

When extensions.voice.enabled=true, incoming Telegram voice/audio attachments are transcribed in best-effort mode and appended into the user instruction.

Group / supergroup

  • Add the bot to the group
  • Group members can send normal messages directly (no @ required).

Notes:

  • If your group uses Topics, replies are sent back to the same topic.
  • For file messages in groups, the caption is treated as instruction text; no extra mention is required.
  • While the agent is running, Telegram shows a periodic typing indicator (sendChatAction) until the result is sent.

Reply-to and Reaction Logic (Important)

This section explains:

  • when outbound sends use reply_to_message
  • when to use chat react instead of a full text reply

1) How reply_to_message is decided

When the agent runs city chat send, default behavior is a normal non-reply send. Runtime only attempts reply_to_message_id when reply mode is explicitly enabled (for example city chat send --reply).

messageId source priority:

  1. explicit input argument (for example --message-id <id>)
  2. latest inbound message metadata stored for the same chatKey (chat meta)

Behavior:

  • valid numeric messageId exists: send with reply_to_message_id
  • no valid messageId: still sends as a normal message (without reply link)
  • same rule applies to text and attachments

2) How chat react works

Use chat react for lightweight acknowledgement/attitude, e.g.:

  • "got it"
  • "agree"
  • quick encouragement

Examples:

# default 👍
city chat react

# custom emoji
city chat react --emoji '🔥'

# explicit target message
city chat react --emoji '✅' --message-id 12345 --chat-key telegram-chat--100xxxx

Behavior:

  • default emoji: 👍
  • --big enables Telegram is_big
  • if --message-id is missing, runtime tries latest inbound messageId from the same chatKey
  • if no valid messageId is available, chat react fails (reactions must target an existing message)
  • Use city chat send for actual user-facing answers.
  • Use city chat react for short acknowledgement/attitude only.
  • Use city chat send --reply only when you explicitly need thread-style targeting in busy chats.
  • For cross-chat actions, always pass --chat-key.
  • In a brand-new chat with no inbound message yet, ask the user to send one message first before reply/react targeting.

Troubleshooting

  • No response at all: confirm the process is running and check .ship/logs/<YYYY-MM-DD>.jsonl (search for TELEGRAM, telegram, or errors).
  • Bot doesn’t receive group messages: in BotFather run /setprivacy and set it to Disable, then confirm the bot can read group messages.
  • Polling doesn’t work: if you previously set a webhook, polling (getUpdates) will conflict. Downcity clears webhook on startup; check logs for webhook-related errors.
  • Bot can’t speak in the group: check group permissions (the bot may be muted / not allowed to send messages).
  • Agent says “I’ll execute” but doesn’t run tools: explicitly ask with execution intent words (for example run, execute, use skill, lookup). Runtime will force a tool call on these turns; if it still fails, verify your model/provider actually supports tool calling.

Table of Contents