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.
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, bridgeThe BotAdapter interface
An adapter implements four methods. The Runner calls them; it never touches protocol types directly.
Run(ctx, handler, commandHandler): drive inbound polling; callhandlerfor normal messages andcommandHandlerfor 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/statusreplies (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 inRun, outbound chunking inSend, a typing signal if the protocol supports one. - Wire up a subcommand (e.g.
zot discord-bot) that constructs the adapter, an agent, andbot.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.