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