Channels let an agent receive and send messages outside the web UI. Connect Telegram, Slack, Discord, WhatsApp, Signal, SMS, or any of the other supported providers, and the agent can reply to incoming messages just like it does in chat, with tools, memory, delegation, and signals all working the same way.
Spaces and agents
Each channel belongs to a space and is bound to a single agent. Conversations on the channel live inside that space, so the agent has access to the same memory and context it would have in the web UI for that space.
You can connect multiple channels to the same agent (for example, your personal assistant on both Telegram and SMS). You can also split channels by space: a finance space with its own SMS channel for bank alerts, a personal space with Telegram for everyday chat.
Dispatch modes
A channel can run in one of two modes:
| Mode | What it does | When to use |
|---|---|---|
| Message (default) | Inbound messages run full inference. The agent reads, replies, and uses tools. | Normal conversation channels. |
| Signal | Inbound messages do tag-only inference and feed the signal matcher. The agent doesn't reply. | Channels for ingesting alerts, 2FA codes, or events that other agents are waiting on. |
A channel can be flipped from one mode to the other in its settings. The two-tier authorization (see below) lets you mix the behaviors per sender.
Pairing and self-source
Channels require pairing before they accept inbound messages. The flow is:
- After connecting, the channel goes to Pairing state and shows a one-time code.
- You text the code from the address you want to use to the channel's number (or send the code from the linked account, depending on the provider).
- The platform records that address as a paired address on the channel.
- From then on, messages from any paired address are treated as you (self-source) and pass the default
receive_messagepolicy.
Messages from non-paired addresses are dropped by default. If you want the agent to talk to other people on the channel, you add a policy that allows specific contacts or addresses.
Authorization
Two policy actions gate every inbound:
| Action | Effect |
|---|---|
receive_message | Allows the agent to read and reply to the message |
receive_signal | Allows the message to be evaluated against active signal watches |
The dispatcher evaluates both:
- Both allowed → full inference. The agent replies and may use tools.
receive_signalallowed butreceive_messagedenied → tag-only inference. The agent annotates the message for signal matching but cannot reply or use general tools.- Both denied → the message is discarded. No DB record, no broadcast.
This is how you get behaviors like "the finance agent never sees SMS at all" or "this channel is for ingesting bank 2FA codes only".
See Policies for the full action reference.
Interactive prompts on channels
When an agent needs your input (an app deploy approval, a multiple-choice question, a credential pick), it pauses the chat and asks. How that prompt arrives depends on the channel:
| Channel kind | Render |
|---|---|
| Telegram, Discord, Slack, WhatsApp Cloud | Real buttons (inline keyboard, Block Kit, interactive message). Tap one and the agent resumes. |
| Signal, WhatsApp Personal, SMS | A prompt followed by "Reply YES or NO". Quote-reply with yes, y, ok, 👍, no, n, nope, ❌ (and many variants). The parser is generous. |
The chat resumes the moment all pending prompts on the latest turn are resolved. Until then, the agent doesn't generate or call other tools.
For credential picks, channels don't render the vault picker inline. The prompt links to a web page where you choose the entry — same chat-pause behavior.
Message length and formatting
Each adapter splits long replies at safe boundaries — paragraph breaks, then line breaks, then word boundaries — so a single agent turn that exceeds a platform limit arrives as a sequence of clean chunks rather than getting truncated:
| Channel | Per-message limit | Notes |
|---|---|---|
| Telegram | 4,096 chars | Markdown is rendered into MarkdownV2. Tables render as monospace code blocks. |
| Discord | 2,000 chars | Markdown is rendered into Discord's flavor. Tables render as monospace code blocks. |
| Slack | Plain text | The adapter multi-chunks silently. |
| SMS | 1,600 chars | Long replies are truncated and finished with Full reply: {short-link} pointing at a share URL for the full conversation. |
| WhatsApp (both) | Per-message limit | Markdown is converted to WhatsApp's inline styles. |
| Signal | Per-message limit | Markdown is converted to Signal's BodyRange format. |
Splitters never break inside a code fence or between escape character and the character it escapes, so syntax stays intact.
Available adapters
| Channel | Connection | Public URL? | Best for |
|---|---|---|---|
| Telegram | Webhook from Telegram | Yes | Personal assistants, group bots |
| Slack | Socket Mode (outbound WebSocket) | No | Team workspaces |
| Discord | Gateway WebSocket | No | Community servers, gaming groups |
| WhatsApp (Cloud API) | Meta webhook | Yes | Business numbers, customer-facing |
| WhatsApp (Personal) | QR linked device | No | Personal use, testing |
| Signal | QR linked device | No | Personal use, 2FA capture |
| SMS | Twilio webhook | Yes | Mobile assistants, 2FA capture, notifications |
Channel statuses
| Status | Meaning |
|---|---|
| Linking | The channel is waiting for an interactive setup step, such as scanning a QR code for Signal or WhatsApp Personal. This is shown as an overlay on the connection status. |
| Pairing | Waiting for the user to complete address pairing (e.g., text the pairing code to the bot) |
| Connecting | The adapter is starting and registering with the provider |
| Connected | The channel is live and receiving messages |
| Reconnecting | The worker lost its connection and is waiting for the next automatic retry |
| Disconnected | The worker is not connected. If the channel is disabled, it stays disconnected until you enable it. |
| Failed | The adapter could not start. See the error message on the channel page. |
Setup, pairing, and connection state are tracked separately. For example, a channel can be enabled while it is still waiting for a QR scan. The Channels page shows the setup or pairing step first, then the underlying connection state once setup is complete.
Enable, disable, and reconnect
Each channel runs in its own supervised worker. Its enabled setting records what you want independently of its current connection status:
- Disable stops the worker and prevents automatic reconnects without deleting the channel or its configuration.
- Enable starts a disabled channel.
- Retry immediately starts an enabled channel that reached Failed after exhausting its retry policy.
If a running connection drops, only that channel enters Reconnecting. Other channels continue operating normally, and the supervisor restarts the failed worker according to its retry policy.
Deleting a channel always stops its worker and removes the channel. The confirmation dialog can optionally delete its associated space and all chats in that space, including archived chats. Leave that option unchecked if you want to keep the conversation history.
Retry policy
When a channel fails to start or its connection drops, Frona retries automatically with exponential backoff. The default policy lives in channel.retry in the config file:
| Field | Default | Meaning |
|---|---|---|
max_retries | unlimited | Total retry attempts before the channel is marked Failed for good. |
initial_backoff_ms | 1000 | Delay before the first retry. |
backoff_multiplier | 2.0 | Delay multiplier between attempts. |
max_backoff_ms | 60000 | Cap on the delay between attempts. |
Every channel can override these defaults on its detail page (Retry policy section). Per-channel values take precedence over the server default. If you want a channel to fail fast (say, a Slack bot that should never silently retry), set max_retries to a small number for that channel.
Tips
- Start in Message mode. Switch to Signal mode only when you specifically want passive watching, not conversation.
- Pair from the address you'll use. The pairing code locks the channel to that wire address. If you want to add another, repeat pairing.
- Use a separate agent per channel if the channels have different audiences. The agent's specialty is what people on the other end interact with.
- Use spaces to scope memory. A channel for work and a channel for personal life should live in different spaces so memory doesn't leak between them.
- Watch the status. A channel in
Failedwon't receive anything until you retry it. A channel inReconnectingis already being recovered automatically.