# 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 case-insensitive 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. New adapters should use `bot.ParseCommand` so command parsing stays consistent across messaging backends.

## 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](/docs/telegram) page. This page covers the shared machinery underneath.
