Skip to main content
NanoClaw trunk ships channel infrastructure — the adapter interface, the registry, the Chat SDK bridge, the webhook server — plus one built-in channel: cli, an always-on local-terminal channel that needs no credentials. Every platform adapter (Telegram, Discord, Slack, WhatsApp, …) lives on the channels branch of the repo and is installed on demand with an /add-<channel> skill. Your fork only contains the channels you actually use.

How a message flows

Adapters self-register with the channel registry on import. At startup the host instantiates every registered adapter whose credentials are present (a factory that finds no credentials returns null and the channel is skipped with a warning). From there, every channel takes the same path: Trunk ships the channel infrastructure and the built-in CLI channel; platform adapters live in the channels-branch catalog and are installed on demand with /add-channel skills; a wiring connects each messaging group to an agent group Trunk ships the channel infrastructure and the built-in CLI channel; platform adapters live in the channels-branch catalog and are installed on demand with /add-channel skills; a wiring connects each messaging group to an agent group The adapter normalizes platform events into inbound messages; the router matches them to a wiring (which agent group handles which chat) and writes them into the right session; the agent’s reply drains through the delivery module, which looks up the owning adapter in the registry — by the messaging group’s instance (which defaults to the channel type) — and calls deliver(), so a reply always leaves through the same adapter the message arrived on. Adapters come in two flavors:
  • Chat SDK adapters: wrap a Chat SDK platform adapter (the chat npm package and its @chat-adapter/* family) via the bridge in src/channels/chat-sdk-bridge.ts. The bridge handles mention detection, attachments, reply context, interactive question cards, and splitting long messages to fit platform limits. Discord, Slack, Telegram, Teams, and most others use this pattern. Mattermost is the variant case: it rides the same bridge, but its adapter is vendored source copied from the channels branch rather than an npm package.
  • Native adapters — implement the ChannelAdapter interface directly. Used where no Chat SDK adapter fits: WhatsApp (Baileys), Signal (signal-cli), Dial (the dial CLI), WeChat, Delta Chat, Emacs, iMessage’s hosted backend (spectrum-ts), and the built-in CLI channel.

What an /add-<channel> skill does

Each install skill follows the same pattern (see /add-telegram for the canonical example):
  1. Fetch the channels branchgit fetch origin channels
  2. Copy the adaptergit show origin/channels:src/channels/<name>.ts > src/channels/<name>.ts, along with its tests, helpers, and any setup steps
  3. Wire the barrel — append import './<name>.js'; to src/channels/index.ts so the adapter self-registers at startup
  4. Install the platform package, pinned — e.g. pnpm install @chat-adapter/telegram@<exact-version> (each skill pins its own version)
  5. Build and validatepnpm run build plus the channel’s registration test, which asserts the adapter actually lands in the registry
  6. Collect credentials — bot tokens, QR pairing, linked devices, whatever the platform needs, into .env
The install steps are designed to be re-run: adapter files are refreshed from the registry and existing imports are not duplicated. Credential steps can reuse an existing connection or offer another identity, depending on the channel. Re-running an /add-<channel> skill is how you pick up adapter improvements from the channels branch.

Adapter catalog

Messaging platforms with install skills. “Setup wizard” means the first-run setup offers the channel and runs its /add-<channel> skill for you; the others are installed by running the skill directly. The built-in cli channel isn’t in this table because it ships on trunk — see CLI channel. The channels branch also contains a local-web browser channel. It listens on 127.0.0.1:3210 by default and protects conversation data and actions with a local token. It is not included in the main setup wizard and has no /add-local-web install skill on trunk.

The webhook server

Chat SDK channels that receive platform events over HTTP (Slack, Teams, GitHub, and others) share one webhook server. It starts lazily when the first such adapter registers and routes by path:
The routing path defaults to the adapter name, so single-instance routes are unchanged; a second adapter of the same platform passes an instance and listens on /webhook/{instance} with its own signing secret. Modules that need a non–Chat SDK endpoint (a custom GitHub webhook, a payment provider, a health check) register a raw handler on the same server with registerWebhookHandler(path, handler) instead of opening a second port — raw routes take priority over adapter routes. The port comes from the WEBHOOK_PORT environment variable, default 3000. Point the platform’s event subscription URL at https://<your-public-host>/webhook/<channel> (e.g. /webhook/slack); you’ll need a public URL or a tunnel for these channels. Discord is the exception: it uses a persistent Gateway listener instead of inbound webhooks, so no public URL is required. Telegram (long polling), WhatsApp, Signal, Dial, and WeChat hold their own outbound connections, so they don’t need one either. Mattermost is a hybrid: chat arrives over its own outbound WebSocket (no public URL), but approval-card clicks come back on /webhook/mattermost; that route only needs to be reachable from the Mattermost server, not from the public internet. Slack needs a public URL only in webhook mode: in Socket Mode (the setup default) it holds an outbound WebSocket like Discord, so no public URL is required.

Wiring channels to agents

Installing a channel gets messages flowing into NanoClaw; wiring decides which agent group handles them. A wiring connects a messaging group (a chat, channel, or thread on a platform) to an agent group — the same chat can be wired to multiple agents and vice versa. Run /manage-channels in Claude Code to do this conversationally. It:
  • Shows current state — wired, configured-but-unwired, and unconfigured channels, plus privileged users
  • Walks you through registering new chats, including platform-specific ID discovery (Telegram uses a pairing code, so you never hunt for chat IDs by hand)
  • Asks the isolation question for each new wiring: same conversation, same agent with separate conversations, or a fully separate agent
  • Moves existing channels between agent groups
For scripted access, the ncl CLI exposes the same data: ncl wirings lists and edits the wiring rows directly. The isolation choice is the session_mode on each wiring — see the entity model for what shared, agent-shared, and per-thread mean. If you’re setting up your very first channel, /init-first-agent handles the whole flow — agent group creation, wiring, owner promotion, and an end-to-end delivery test.

Channel guides

WhatsApp

Baileys or Cloud API

Telegram

BotFather token, pair-code registration

Discord

Gateway listener, threads

Slack

Managed apps, agent rooms, canvases

Mattermost

Your server or a local evaluation stack

Signal

signal-cli linked device

iMessage

macOS local or remote mode

Microsoft Teams

Bot registration, webhook

Dial

Real phone number: SMS and voice

CLI

Built-in terminal channel

More channels

Matrix, GitHub, Linear, WeChat, and the rest
Last modified on September 4, 2026