Clapilot-Agent Channels
Native channel pipeline for Telegram, Slack, WhatsApp, Signal, and iMessage.
Channel architecture explained visually
These raster diagrams are generated documentation assets and are paired with exact captions below so the page is readable even when a tiny in-image label is hard to inspect.

Shows provider ingress, parsing, event deduplication, thread resolution, approval gating, native execution, and reply delivery.

Explains how DMs, topics, Slack threads, and WhatsApp groups can remain channel-local or map onto linked users and team rooms.

Compares Telegram polling and streaming edits, Slack webhooks, and WhatsApp Web QR/auth-state transport before all paths merge into native channels.
Channels are how people reach the same Clapilot agent from outside the web app. The important idea is that external messages are not trusted just because they arrive. Every provider event is parsed, deduplicated, checked against the approval model, mapped to a session, and only then allowed to trigger a runtime run.
Use this page when Telegram, Slack, WhatsApp, Signal, or iMessage behavior differs from web chat, or when you need to understand why an external thread did or did not receive a reply.
The native runtime supports five channel types in agent_channel_configs:
telegramslackwhatsappsignalimessage
Main implementation:
services/clapilot-agent/src/channels/index.mjsservices/clapilot-agent/src/channels/whatsapp-web.mjsservices/clapilot-agent/src/channels/signal-imessage.mjs
Shared channel pipeline
All inbound channels follow the same core flow:
- load channel config from
agent_channel_configs - parse the provider-specific inbound payload
- deduplicate using the external event id recorded in
agent_events - resolve or create a thread record in
agent_channel_threads - upsert an approval record in
agent_channel_approvals - block unapproved senders/groups or blocked DMs
- optionally ignore group/channel messages that do not mention Clapilot when the per-channel mention-only setting is enabled
- run the conversation through Clapilot delegation or the native session engine
- send the reply back through the provider API
When a repo/coding request is detected in an approved channel thread, the runtime can now route into the native Agent Orchestrator session broker instead of the normal native chat loop:
- existing interactive session for that thread: continue it
- no session yet but the message looks like repo/coding work: create one and bind it to the thread
- normal business/document conversation: stay on the standard native session path
Thread identity is normalized to one of two session-key modes:
- fallback channel-local key:
channel:<channel>:<threadKey> - mapped canonical key: the same
agent:main:openai-user:<session_user>key family used by web chat and team chat
Approved Telegram/Slack/WhatsApp/Signal/iMessage threads can therefore be mapped onto existing Clapilot session identities instead of always living in a channel-local memory silo.
For Telegram forum/group topics, threadKey is now topic-aware:
- direct messages:
<chatId> - groups without topic id:
<chatId>:root - forum/topic messages:
<chatId>:<message_thread_id>
Approval scope stays anchored to the parent Telegram chat/group id, but session/memory scope is split per topic.
Security and approval model
The runtime never auto-replies to unknown external subjects.
Approval data is stored in agent_channel_approvals with:
channel_typesubject_typedmorgroupsubject_keythread_keysender_keystatuspending,approved,denied
Behavior:
- new DM or group arrival creates or refreshes an approval record
- until approved, the runtime logs the event but does not answer
- direct messages can also be globally blocked per channel with
allow_direct_messages = false - approved groups/channels can be switched to mention-only mode per channel with
settings.mention_only = true; DMs are unaffected - approved DMs should be linked to a specific Clapilot user; the channel thread then reuses that user's main-chat session key
- approved groups can be mapped to any non-archived public/private team-chat channel or group room; the room picker offers every mappable room available to the admin, with
clapilot-membersfirst, and the channel thread then reuses the selected room's team-chat session key - approved mappings are mutable; admins can later change the linked user / room or move the entry back to
pending
Outbound sends from normal agent runs are also constrained to approved recipients. Recipient matching uses stored keys and metadata such as usernames, display labels, Slack channel ids, or WhatsApp phone ids.
When an outbound send is delivered through an approval, the runtime also records the sent assistant text into the mapped conversation session from approval metadata (session_key, or the mapped group room when only group_room_id is available). Sent Telegram media is recorded as workspace file references in that same internal context entry. Sends into the run's own session are not double-recorded because the run output already captures that assistant turn.
Successful /internal/channels/send responses include group_room_id for the Team Chat room mapped by the matched approval and mirrored_room_id only when the outbound message was actually persisted in that room. Callers, including automation deduplication, must use mirrored_room_id rather than assuming that a configured mapping proves the mirror succeeded.
Team-Chat-Spiegelung
Approved Telegram, WhatsApp, Slack, Signal, and iMessage groups can opt into one-way mirroring from the external channel into any mapped public/private Team Chat channel or group room. The approval room picker offers all non-archived mappable rooms available to the admin. Mirroring is enabled only when the approval row has an explicit, non-empty metadata.group_room_id; the implicit clapilot-members session fallback does not enable it.
- inbound participant messages appear as user messages in the mapped room, including non-mention chatter when mention-only mode suppresses an agent run
- a sender is attributed to a real Clapilot user when an approved DM mapping for the same channel sender contains
metadata.linked_user_id; otherwise the message uses a synthetic channel display name based on the external name, username, or sender key - delivered agent replies are mirrored for normal auto-replies,
channel_send_messagetool calls, and automation sends - inbound Telegram and WhatsApp photos/documents plus best-effort Signal and iMessage images are persisted under
.clapilot/channel-media/<channel>/<yyyy-mm>/in the shared workspace and appear as real Team Chat attachments; downloads and payloads are limited to 20 MB per attachment - inbound files that cannot be persisted or exceed the cap retain a compact
📎text placeholder; outbound workspace media appears as the same real Team Chat attachment shape without a duplicate placeholder - the mapped room's built-in main-agent membership is authoritative for mirrored inbound messages: removed means no default channel reply,
mention_onlyrequires an explicit main-agent@mention, andall_messageskeeps the default reply path active unless the message explicitly targets an invited specialist; an explicit specialist target suppresses the default main-agent run unless the main agent is also explicitly mentioned - voice notes remain transcript-only in Team Chat and are not copied as audio attachments
- mirrored user and agent messages show the small channel brand icon next to the sender name in the Team Chat UI, with a localized “sent via” tooltip
The reverse direction is separately opt-in per approved group mapping through metadata.mirror_to_channel = true in the approval room picker. When enabled, Team Chat user messages are posted to the mapped external group as [Name via Clapilot] followed by a newline and the message text; agent replies are posted as plain bot messages without a prefix. Telegram receives real image/document attachments from Team Chat: base64 attachments are materialized under .clapilot/channel-media/outbound/<yyyy-mm>/ in the shared workspace, workspace-backed attachments reuse their relative path, and each file is limited to 20 MB. For reverse sends that contain text and media, Telegram receives the text first and then the files. WhatsApp, Slack, Signal, and iMessage receive 📎 <name> placeholders instead of outbound media, and Telegram uses the same placeholder for attachments that cannot be materialized or exceed the cap.
Rows whose message_meta.channelOrigin is set are never forwarded back to the external group. Reverse sends also use messageOrigin: "team_chat_forward", which tells sendOutbound to skip its normal external-channel-to-Team-Chat display copy. Together these guards prevent echo loops and duplicate Team Chat rows while leaving provider delivery and mapped-session behavior unchanged.
Automations should target either the Team Chat room or the channel approval when reverse mirroring is enabled. If an automation explicitly targets both, the external group can receive two intentional deliveries: the direct channel delivery and the reverse-mirrored Team Chat delivery.
Telegram
Telegram is the most complete native channel path today.
Default mode is polling:
- the runtime uses
getUpdates - it clears any webhook for the active token when polling is enabled
- it maintains a watchdog and aborts stalled polls
Optional mode is webhook:
- Telegram posts to Clapilot’s public route
- the Next.js app forwards internally to
clapilot-agent
Streaming reply behavior is native. The accepted settings.streaming values are partial, block, and off (off is normalized to the internal value none; both spellings disable preview streaming):
partialTelegram uses one visible reply message and edits that same message in place while deltas arrive Telegram photo/document attachments are downloaded byclapilot-agentand normalized into native multimodal message parts before the runblockcompatibility preview modeoffno native preview streaming
Telegram-specific settings live in agent_channel_configs.settings, for example:
{
"transport": "polling",
"streaming": "partial"
}
The runtime also sends typing indicators and falls back from unsupported draft APIs to normal sendMessage plus editMessageText.
Telegram + interactive orchestrator details:
- outbound replies still target the original
message_thread_id - interactive coding sessions are bound by the Clapilot-owned topic-aware
threadKey, not by Telegram-native ACP support - session bindings are persisted in
agent_external_sessionsand detailed lifecycle/tool/terminal events are audited inagent_events - auto-routing into the interactive orchestrator is intentionally conservative: repo/coding work starts sessions, and existing sessions only continue on coding-like follow-up messages, not on meta questions about ACP/Telegram/session capabilities
- if a thread already has an active coding session and the user asks a meta status question about that session, the channel runtime answers from the real session state instead of forwarding the question into the coding agent or guessing from generic model knowledge
- retry-style follow-ups in a thread with an existing orchestrator binding, such as
versuchs nochmalortry again, are treated as orchestrator continuations; if the previous session was already closed, the runtime starts a fresh replacement session for the same thread key
Detailed Telegram page:
WhatsApp Web is connected through the backend-owned Baileys QR session. Inbound push-to-talk voice notes and regular audio messages are downloaded by clapilot-agent, persisted under .clapilot/channel-media/whatsapp/<year-month>/, and transcribed through the configured Clapilot STT runtime before the normal channel run starts. The transcript becomes the effective user message; raw audio is not sent to the model as a generic file.
When the approved WhatsApp group is mapped to a Team Chat room, the same workspace audio file is mirrored as a playable audio attachment together with the transcript. If download, persistence, or STT fails, the runtime records whatsapp_audio_transcription_failed, keeps a successfully persisted audio attachment visible in the mapped room, and returns a concrete channel error instead of passing <media:audio> to the agent.
Slack
Slack inbound handling is webhook-driven.
Current behavior:
- handles
url_verification - parses
event.channel_type - distinguishes DM vs channel/thread
- builds thread identity as
<channel>:<thread_ts|ts> - sends replies with
chat.postMessage
Slack-specific implementation details:
- thread continuity is preserved through
thread_ts - DM approval is per Slack user or DM thread
- channel approval is per Slack channel id
- mention-only mode treats Slack
app_mentionevents as explicit mentions
The runtime currently uses the saved bot token from agent_channel_configs.credential_*.
Signal
Signal uses an external signal-cli-rest-api service that the administrator operates separately. Clapilot does not register or link Signal accounts itself.
Prerequisites:
- run
bbernhard/signal-cli-rest-apiin a mode whereGET /v1/receive/{number}is available - link the account in the external service, for example through
{baseUrl}/v1/qrcodelink?device_name=Clapilot - keep the service reachable from the
clapilot-agentruntime
Channel settings:
{
"baseUrl": "http://192.168.1.10:8080",
"number": "+49123456789",
"pollIntervalMs": 2000
}
Runtime behavior:
- ClapilotAICore long-polls
GET {baseUrl}/v1/receive/{number}?timeout=20 - polling uses a hard timeout, watchdog, and bounded exponential error backoff
- receipts, typing events, sync envelopes, and empty data messages are ignored
- DMs use the source number/UUID as their thread and reply key
- groups retain the exact inbound
dataMessage.groupInfo.groupId, including agroup.prefix when supplied, so replies round-trip throughPOST {baseUrl}/v2/send - image attachments are fetched best-effort from
GET {baseUrl}/v1/attachments/{id}and become multimodal image parts when available
Every new Signal DM or group must pass the same channel approval flow before it can trigger a reply. Direct messages also respect allow_direct_messages; approved groups respect mention-only mode. Outbound messages are text-only. Signal registration, outbound media, reactions, receipts, and typing indicators are intentionally not managed by Clapilot.
iMessage via BlueBubbles
iMessage uses an external BlueBubbles Server running on a Mac. Clapilot receives BlueBubbles webhooks and sends replies through its REST API; it does not automate BlueBubbles installation or Messages.app setup.
Prerequisites:
- install and configure BlueBubbles Server on a Mac signed into Messages
- make the server reachable from
clapilot-agent - create a BlueBubbles webhook for
new-messagepointing athttps://<clapilot>/api/agent-runtime/channels/imessage/inbound?password=<BlueBubbles-password>
Channel configuration uses settings.serverUrl; the BlueBubbles password is stored in Clapilot's encrypted channel credential columns rather than plaintext settings JSON:
{
"serverUrl": "https://bluebubbles.example.com"
}
Webhook authentication is mandatory. BlueBubbles versions and clients may name the query parameter password or guid; Clapilot accepts either name and requires its value to match the saved encrypted password. Missing or mismatched values receive 403 and are never parsed as messages.
Runtime behavior:
- only
type: "new-message"events are accepted - messages with
data.isFromMe === true, missing chat GUIDs, or no text/image are ignored data.guidis the deduplication keydata.chats[0].guidis retained as the DM/group thread and outboundchatGuid- the sender label comes from
data.handle.address - image attachments are fetched best-effort from
GET {serverUrl}/api/v1/attachment/{guid}/download?password=... - replies first call
POST {serverUrl}/api/v1/message/text?password=...withmethod: "private-api"; a failed Private API request is retried once withmethod: "apple-script"
Every new iMessage DM or group must be approved before Clapilot replies. Direct messages also respect allow_direct_messages; approved groups respect mention-only mode. Outbound messages are text-only. Reactions, delivery/read receipts, typing, outbound media, and BlueBubbles setup automation are not included.
The runtime currently supports a backend-owned WhatsApp Web session based on the Baileys client.
Important points:
- pairing is done by QR from the ClapilotAICore settings UI
- auth state is persisted under
.clapilotaicore/channels/whatsapp/<account> - the runtime maintains the connection and reconnect loop itself
- outbound sends go through the active Web session
- mention-only mode compares WhatsApp group mention JIDs against the linked account JID
Runtime auth/status endpoints:
GET /internal/channels/whatsapp/authPOST /internal/channels/whatsapp/auth
Supported auth actions:
startwaitlogout
The codebase still contains a generic webhook parser for WhatsApp-shaped payloads in channels/index.mjs, but the actively managed native path is the backend-owned WhatsApp Web module in whatsapp-web.mjs.
If you still expose Clapilot's public WhatsApp inbound route for a Meta-style webhook compatibility path, the app now answers GET /api/agent-runtime/channels/whatsapp/inbound verification requests directly. Configure the verify token in the persisted channel settings JSON under webhook_verify_token, webhookVerifyToken, verify_token, or verifyToken.
Transport strategy
Channel execution can take two reply paths:
- preferred: delegate into Clapilot through
/api/agent-runtime/channel-response - fallback: run directly in the native session engine
Why that split exists:
- Clapilot delegation reuses the broader app/business action layer
- native fallback keeps the channel operational if that bridge is unavailable
Telegram streaming is an exception: when native streaming is enabled, the reply path is handled directly inside clapilot-agent so preview updates can be emitted during generation.
Configuration model
Per-channel config lives in agent_channel_configs:
enabledallow_direct_messagesinbound_path- encrypted
credential - provider-specific
settingsJSON, includingmention_only = truefor group/channel mention-only replies
Seeded default rows are created for all five supported channels.
Admin UI responsibilities:
- save credentials
- toggle enablement and DM policy
- toggle group/channel mention-only behavior
- review and approve pending contacts/threads
- manage WhatsApp QR pairing
Data written by the runtime
Channel events and metadata are stored across:
agent_channel_configsagent_channel_approvalsagent_channel_threadsagent_eventsagent_runsagent_session_state
This separation matters:
- config is static admin state
- approvals are security decisions
- threads bind external thread ids to internal
session_key - runs/events provide diagnostics and auditability
Operational notes
- Telegram polling is the default and requires no public webhook.
- Signal polling requires network access from ClapilotAICore to the external signal-cli-rest-api service.
- Slack, iMessage, and other webhook-style ingress require a public Clapilot endpoint because the app forwards into the internal service.
- WhatsApp Web is stateful and depends on persisted auth files inside
.clapilotaicore.
Related docs:
