Skip to content

Message Routing ​

The message routing layer is responsible for delivering the Agent's replies through the correct channel, in the correct format, and within the correct limits.

Outbound Delivery Flow ​

Source: src/infra/outbound/deliver.ts

Channel Handler ​

typescript
// src/infra/outbound/deliver.ts (simplified)
interface OutboundSendDeps {
  sendWhatsApp: SendFunction;
  sendTelegram: SendFunction;
  sendDiscord: SendFunction;
  sendSlack: SendFunction;
  sendSignal: SendFunction;
  sendIMessage: SendFunction;
  // ...
}

function createChannelHandler(channel: string) {
  return {
    chunker: resolveChunker(channel),        // Text splitting function
    chunkerMode: resolveChunkerMode(channel), // "text" | "markdown"
    textChunkLimit: resolveChunkLimit(channel),
    sendText: (opts) => /* ... */,
    sendMedia: (opts) => /* ... */,
    sendPayload: (opts) => /* ... */,
  };
}

Each channel's handler includes:

PropertyDescription
chunkerText chunking function (null = no chunking)
chunkerModeChunking mode: "text" or "markdown"
textChunkLimitMaximum characters per message
sendText()Send plain text
sendMedia()Send media attachments
sendPayload()Send structured content

Chunking Strategies ​

Source: src/auto-reply/chunk.ts

Long messages need to be split into multiple sends. There are two chunking strategies:

Text Chunking ​

typescript
// Simple text chunking
function chunkByParagraph(text: string, limit: number): string[] {
  // Split by paragraphs
  // Respect limit per chunk
  // Avoid splitting mid-sentence
}

Markdown Chunking ​

typescript
// Markdown-aware chunking
function chunkMarkdownTextWithMode(
  text: string,
  limit: number,
  mode: "text" | "markdown"
): string[] {
  // Preserve code blocks across chunks
  // Keep link syntax intact
  // Maintain list structure
  // Handle heading boundaries
}

Markdown chunking is smarter and will:

  • Never break inside ``` code blocks
  • Never break inside [link](url) syntax
  • Prefer breaking at paragraph or heading boundaries
  • Maintain list continuity

Per-Channel Limits ​

ChannelText LimitChunking Mode
Telegram4000 charsmarkdown
Discord2000 charsmarkdown
WhatsAppVariestext
SlackVariesmarkdown
SignalVariestext
iMessageVariestext

Idempotency ​

Source: src/gateway/server-methods/send.ts

Message sending supports idempotency deduplication to prevent duplicate messages caused by network retries:

typescript
// Idempotency deduplication
if (idempotencyKey && seen.has(idempotencyKey)) {
  return respond(true, seen.get(idempotencyKey));
}

CLI Deps Bridging ​

Source: src/cli/deps.ts

CLI-layer dependencies are bridged to the infrastructure layer via createOutboundSendDeps():

typescript
// src/cli/deps.ts (simplified)
interface CliDeps {
  sendMessageWhatsApp: SendFunction;
  sendMessageTelegram: SendFunction;
  sendMessageDiscord: SendFunction;
  sendMessageSlack: SendFunction;
  sendMessageSignal: SendFunction;
  sendMessageIMessage: SendFunction;
}

function createDefaultDeps(): CliDeps {
  // Create concrete implementations
}

function createOutboundSendDeps(deps: CliDeps): OutboundSendDeps {
  // Bridge CLI deps → infra OutboundSendDeps
}

This bridging pattern allows the CLI and infrastructure layers to be tested independently.

Summary ​

  • deliverOutboundPayloads() is the unified entry point for outbound delivery
  • Each channel provides a send function and chunking strategy via createChannelHandler()
  • Chunking supports text and markdown modes; markdown mode preserves syntax integrity
  • Idempotency deduplication uses idempotencyKey caching to prevent duplicate messages
  • CLI dependencies are connected to the infrastructure layer through a bridging pattern

Next: Agent Overview

OpenClaw Source Code Tutorial