Skip to content

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.

Every channel entry accepts these fields regardless of type:

FieldDefaultDescription
namerequiredUnique identifier — used in bindings and capability labels
typerequiredConnector type (see below)
enabledtrueDisable a channel without removing it from config
showToolCallsfalseSend 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}

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 fieldDescription
botTokenBot token from @BotFather
pollingTimeoutSecLong-poll timeout in seconds (default: 30)
allowedChatIdsWhitelist of chat IDs that can message the bot
allowGroupChatsAllow group and supergroup chats (default: false)
triggerWordsPrefix 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.

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:

  1. @mentioned by username — detected via Telegram’s entities array for exact matching (@talon won’t match @talon2)
  2. A triggerWords prefix 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 fieldDescription
botTokenBot user OAuth token (xoxb-...) — needs chat:write, app_mentions:read
appTokenApp-level token (xapp-...) — needs connections:write for Socket Mode
signingSecretSigning secret for request verification
defaultChannelOptional fallback channel ID for cross-channel sends that do not include a Slack channel ID
botUserIdOptional 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.

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: U01XXXXXXX

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:

Terminal window
npm install @whiskeysockets/baileys

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)

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 word

With triggerWords, the trigger is stripped before the message reaches the agent. @Talon what's the weather? becomes what's the weather?.

Authenticate by scanning a QR code before starting the daemon:

Terminal window
npx talonctl whatsapp-auth --auth-dir ./baileys-auth

Scan 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.

WhatsApp uses opaque LID identifiers (96490886312027@lid) that don’t match phone numbers directly. To find them:

  1. Set logLevel: debug in talond.yaml
  2. Restart the daemon
  3. Send a test message from the phone you want to allow
  4. Find the log line whatsapp-baileys: inbound message received — the jid field shows the full ID
  5. Copy the part before the @ into allowedSenders
  6. Set logLevel: info and restart

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.


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 fieldDescription
portWebSocket server port (default: 7700)
hostBind address
tokenShared secret for authentication

Connect:

Terminal window
export TERMINAL_TOKEN=your-secret-token
talonctl chat --host localhost --port 7700
talonctl chat --host 10.0.1.95 --port 7700 --persona researcher
talonctl 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.


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.


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.

  • 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-slack and production-slack connector bound to different personas.

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: true

All connectors automatically filter their own outbound messages to prevent feedback loops — no configuration needed:

ConnectorHow it filters
SlackDrops bot messages. In shared channels, top-level messages must @mention the connector’s own bot user ID; DMs and thread replies always pass
DiscordDrops all messages from author.bot accounts
TelegramResolves 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 BaileysJID-based self-detection
Email / TerminalNot 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. :::

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.


Terminal window
# Add a channel (interactive prompts for config fields)
npx talonctl add-channel --name my-telegram --type telegram
# List all configured channels
npx talonctl list-channels
# Remove a channel (cascades to bindings)
npx talonctl remove-channel --name old-slack
# Disable without removing (edit talond.yaml: enabled: false)