Messaging backends

The protocol-agnostic bot core behind Telegram and future transports.

zot's messenger functionality is split in two layers: a generic bot core that knows nothing about any specific chat protocol, and thin transport adapters that speak one protocol each. Telegram is the first adapter; Discord, Slack, Signal, and similar backends can be added without touching the core.

Architecture

The generic core lives in packages/agent/modes/bot. Its Runner owns turn queueing (messages arriving while the agent is busy are queued and drained in order), agent prompting, built-in command dispatch, status formatting, and per-turn credential refresh. Transports live in sibling packages (packages/agent/modes/telegram today) and implement the BotAdapter interface.

layout
modes/bot/          generic, protocol-agnostic
  adapter.go        BotAdapter interface + InboundMessage/Command types
  runner.go         turn queue, agent prompting, command dispatch
  status.go         status formatting
  commands.go       command parsing

modes/telegram/     Telegram-specific
  adapter.go        implements BotAdapter
  api.go            Telegram API client
  ...               config, daemon, bridge

The BotAdapter interface

An adapter implements four methods. The Runner calls them; it never touches protocol types directly.

  • Run(ctx, handler, commandHandler): drive inbound polling; call handler for normal messages and commandHandler for built-in commands. Blocks until the context is done.
  • Send(ctx, channelID, text, opts): deliver a reply. The adapter chunks to protocol limits and may anchor to a reply message ID.
  • IndicateWorking(ctx, channelID): fire a typing indicator; returns a stop func. Return a no-op if the protocol has none.
  • StatusText(): append protocol-specific info to /status replies (e.g. the bot's handle). Return an empty string if there is nothing to add.

Inbound traffic is normalised into InboundMessage: a channel ID, an optional message ID for reply anchoring, text, and image blocks for vision-capable models. Channel IDs are opaque strings owned by the adapter (Telegram encodes its numeric chat ID as a string), so the shared runner stays free of protocol-specific types.

Built-in commands

Four commands bypass the agent and are handled by the runner: /start (first-time pairing and welcome), /help (usage), /status (agent and provider state), and /stop (cancel the active turn). Everything else becomes a prompt.

Adding a new backend

  • Create a new package, e.g. packages/agent/modes/discord/.
  • Implement BotAdapter: polling or gateway connection in Run, outbound chunking in Send, a typing signal if the protocol supports one.
  • Wire up a subcommand (e.g. zot discord-bot) that constructs the adapter, an agent, and bot.NewRunner(adapter, agent, cfg).

No changes to the runner, agent, or core are required. Credential refresh, queueing behaviour, and command handling come for free from the shared runner, so every backend behaves consistently.

The Telegram bridge and daemon are documented on the Telegram page. This page covers the shared machinery underneath.