Channels
A channel is a messaging connector. Talon normalizes inbound messages from every channel into a common format, routes them to the bound persona, and converts responses back to the channel’s native formatting.
Common options
Section titled “Common options”Every channel entry accepts these fields regardless of type:
| Field | Default | Description |
|---|---|---|
name | required | Unique identifier — used in bindings and capability labels |
type | required | Connector type (see below) |
enabled | true | Disable a channel without removing it from config |
showToolCalls | false | Send a status message on each tool call (useful for debugging) |
channels: - name: my-slack type: slack enabled: true showToolCalls: true # sends "🌐 Using Brave Search: web search" on each tool call config: botToken: ${SLACK_BOT_TOKEN} appToken: ${SLACK_APP_TOKEN}Telegram
Section titled “Telegram”Long-polling connector using the Telegram Bot API. Create a bot with @BotFather and get a token.
channels: - name: my-telegram type: telegram enabled: true config: botToken: ${TELEGRAM_BOT_TOKEN} pollingTimeoutSec: 30 allowedChatIds: - 123456789 # your Telegram user ID| Config field | Description |
|---|---|
botToken | Bot token from @BotFather |
pollingTimeoutSec | Long-poll timeout in seconds (default: 30) |
allowedChatIds | Whitelist of chat IDs that can message the bot |
allowGroupChats | Allow group and supergroup chats (default: false) |
triggerWords | Prefix words that trigger a response; matched prefix is stripped before the message reaches the agent |
Format: MarkdownV2. Bold, italic, inline code, and code blocks render natively in Telegram.
Thread mapping: Each Telegram chat_id is a separate conversation thread with its own memory. In group chats, all messages in the same group share one thread.
Group chats
Section titled “Group chats”Add the bot to a Telegram group or community and have it respond only when addressed:
channels: - name: my-telegram type: telegram enabled: true config: botToken: ${TELEGRAM_BOT_TOKEN} allowGroupChats: true triggerWords: - '@Talon' - '!talon'In groups, the bot only responds when:
- @mentioned by username — detected via Telegram’s
entitiesarray for exact matching (@talonwon’t match@talon2) - A
triggerWordsprefix matches — falls back if no mention is found
The matched mention or trigger word is stripped before the message reaches the agent, so @Talon what's the weather? becomes what's the weather?.
In direct messages, triggerWords also applies when configured; otherwise all messages pass through.
Event-driven connector using Slack’s Socket Mode. Requires a Slack app with the appropriate OAuth scopes.
channels: - name: my-slack type: slack enabled: true config: botToken: ${SLACK_BOT_TOKEN} appToken: ${SLACK_APP_TOKEN} signingSecret: ${SLACK_SIGNING_SECRET}| Config field | Description |
|---|---|
botToken | Bot user OAuth token (xoxb-...) — needs chat:write, app_mentions:read |
appToken | App-level token (xapp-...) — needs connections:write for Socket Mode |
signingSecret | Signing secret for request verification |
defaultChannel | Optional fallback channel ID for cross-channel sends that do not include a Slack channel ID |
botUserId | Optional bot user ID (U...). Validated against auth.test at startup and used for shared-channel @mention filtering |
Required Slack app permissions:
- OAuth scopes:
app_mentions:read,channels:history,chat:write,im:history,im:write - Event subscriptions:
message.im,app_mention - Enable Socket Mode in your app settings
Format: Slack mrkdwn (*bold*, _italic_, `code`).
Thread mapping: channel_id:thread_ts — threads are preserved as expected in Slack.
Shared-channel behavior
Section titled “Shared-channel behavior”When multiple Slack bots share the same channel, Talon keeps them from all replying at once:
- Top-level channel messages are only processed if they @mention that connector’s own bot user ID
- Thread replies always pass through without requiring a fresh @mention
- DMs always pass through without requiring an @mention
- Messages from bots are always dropped
If you know the bot user ID up front, set botUserId in config. Talon validates the token-to-bot mapping during startup and fails fast if they do not match:
channels: - name: slack-engineer type: slack enabled: true config: botToken: ${SLACK_ENGINEER_BOT_TOKEN} appToken: ${SLACK_ENGINEER_APP_TOKEN} signingSecret: ${SLACK_ENGINEER_SIGNING_SECRET} botUserId: U01XXXXXXXWhatsApp Baileys
Section titled “WhatsApp Baileys”WhatsApp Web bridge via the Baileys library. No Meta Business account needed — authenticates like a regular WhatsApp Web client.
Baileys is an optional dependency. Install it before using this connector:
npm install @whiskeysockets/baileysDedicated number
Section titled “Dedicated number”Use a second WhatsApp number as the bot account. Others message the bot number directly.
channels: - name: my-whatsapp type: whatsappBaileys enabled: true config: authDir: ./baileys-auth allowedSenders: - '96490886312027' # sender ID from logs (see below)Self-chat mode
Section titled “Self-chat mode”Use your personal WhatsApp number. The bot only responds in your “Message Yourself” thread.
channels: - name: my-whatsapp type: whatsappBaileys enabled: true config: authDir: ./baileys-auth selfChat: true triggerWords: - '@Talon' # optional — only process messages starting with this wordWith triggerWords, the trigger is stripped before the message reaches the agent. @Talon what's the weather? becomes what's the weather?.
Authentication
Section titled “Authentication”Authenticate by scanning a QR code before starting the daemon:
npx talonctl whatsapp-auth --auth-dir ./baileys-authScan the QR code in WhatsApp on your phone. Credentials are saved to authDir. The daemon uses them automatically on startup — no QR code at runtime.
To re-authenticate: delete the authDir folder and run the command again.
Finding sender IDs
Section titled “Finding sender IDs”WhatsApp uses opaque LID identifiers (96490886312027@lid) that don’t match phone numbers directly. To find them:
- Set
logLevel: debugintalond.yaml - Restart the daemon
- Send a test message from the phone you want to allow
- Find the log line
whatsapp-baileys: inbound message received— thejidfield shows the full ID - Copy the part before the
@intoallowedSenders - Set
logLevel: infoand restart
Group chats
Section titled “Group chats”Add the bot to a WhatsApp group and have it respond only when a message starts with a configured trigger word:
channels: - name: my-whatsapp type: whatsappBaileys enabled: true config: authDir: ./baileys-auth allowGroupChats: true triggerWords: - '@Talon' allowedSenders: - '31612345678'WhatsApp Baileys has no native mention entity format, so group messages require at least one triggerWord. Messages without a matching trigger are silently dropped. If you enable allowGroupChats without configuring triggerWords, the connector logs a warning and drops all group messages.
For group messages, allowedSenders checks the individual sender’s JID (from key.participant), not the group JID. This means you can restrict which group members the bot responds to, even in a large community.
All messages in the same WhatsApp group share one conversation thread.
Terminal
Section titled “Terminal”WebSocket-based channel for direct CLI access. Connect with talonctl chat from any machine.
channels: - name: my-terminal type: terminal enabled: true config: port: 7700 host: 0.0.0.0 token: ${TERMINAL_TOKEN}| Config field | Description |
|---|---|
port | WebSocket server port (default: 7700) |
host | Bind address |
token | Shared secret for authentication |
Connect:
export TERMINAL_TOKEN=your-secret-token
talonctl chat --host localhost --port 7700talonctl chat --host 10.0.1.95 --port 7700 --persona researchertalonctl chat --host localhost --port 7700 --client-id my-laptop--persona switches which persona handles the session. --client-id gives your client a stable identity — reconnecting with the same ID resumes the same thread.
The client renders Markdown via marked-terminal and shows a spinner while the agent is working.
Discord
Section titled “Discord”Gateway-based connector. Send is implemented; inbound reception from the Discord Gateway is not yet complete (see TASK-043).
channels: - name: my-discord type: discord enabled: true config: botToken: ${DISCORD_BOT_TOKEN} applicationId: '123456789' allowedChannelIds: - '987654321'IMAP polling + SMTP send. Not yet tested end-to-end — treat as beta.
channels: - name: my-email type: email enabled: true config: imapHost: imap.gmail.com imapPort: 993 imapUser: agent@example.com imapPass: ${EMAIL_PASSWORD} imapSecure: true smtpHost: smtp.gmail.com smtpPort: 587 smtpUser: agent@example.com smtpPass: ${EMAIL_PASSWORD} smtpSecure: false fromAddress: 'Talon <agent@example.com>'Thread tracking via In-Reply-To / References headers. Outbound format is Markdown converted to HTML.
Multi-connector setup
Section titled “Multi-connector setup”You can run multiple instances of the same connector type — for example, two Telegram bots or three Slack bots — each with its own credentials and persona binding. Channels are keyed by name (unique), not by type, so the infrastructure treats them as entirely independent connectors.
Use cases
Section titled “Use cases”- Virtual team — deploy per-persona bots in a single Slack workspace:
pm-bot,dev-bot,content-bot, each responding in character. - Per-persona Telegram bots — multiple bots in a shared group, each bound to a different persona.
- Environment separation — a
staging-slackandproduction-slackconnector bound to different personas.
Configuration
Section titled “Configuration”Add multiple entries of the same type under channels:, give each a unique name, and bind each to a persona:
channels: - name: slack-pm type: slack enabled: true config: botToken: ${SLACK_PM_BOT_TOKEN} appToken: ${SLACK_PM_APP_TOKEN} signingSecret: ${SLACK_PM_SIGNING_SECRET}
- name: slack-dev type: slack enabled: true config: botToken: ${SLACK_DEV_BOT_TOKEN} appToken: ${SLACK_DEV_APP_TOKEN} signingSecret: ${SLACK_DEV_SIGNING_SECRET}
bindings: - persona: product-manager channel: slack-pm isDefault: true - persona: developer channel: slack-dev isDefault: trueBot-self filtering
Section titled “Bot-self filtering”All connectors automatically filter their own outbound messages to prevent feedback loops — no configuration needed:
| Connector | How it filters |
|---|---|
| Slack | Drops bot messages. In shared channels, top-level messages must @mention the connector’s own bot user ID; DMs and thread replies always pass |
| Discord | Drops all messages from author.bot accounts |
| Telegram | Resolves its own bot identity on startup via getMe, then drops messages where from.is_bot is true or the sender ID matches a sibling Talon bot |
| WhatsApp Baileys | JID-based self-detection |
| Email / Terminal | Not applicable |
When multiple Telegram connectors run in the same daemon, Talon automatically distributes sibling bot IDs across all Telegram instances so each bot ignores the others. This also runs on hot-reload.
:::caution WhatsApp Business WhatsApp Business (Cloud API) does not implement bot-self filtering. Avoid running multiple Talon bots sharing the same WhatsApp Business account. :::
Channel routing
Section titled “Channel routing”The channel_send tool routes by channel name. A persona bound to slack-pm sends via the PM bot identity; a persona bound to slack-dev sends via the Dev bot identity. No extra config required.
Managing channels
Section titled “Managing channels”# Add a channel (interactive prompts for config fields)npx talonctl add-channel --name my-telegram --type telegram
# List all configured channelsnpx talonctl list-channels
# Remove a channel (cascades to bindings)npx talonctl remove-channel --name old-slack
# Disable without removing (edit talond.yaml: enabled: false)