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
// 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:
| Property | Description |
|---|---|
chunker | Text chunking function (null = no chunking) |
chunkerMode | Chunking mode: "text" or "markdown" |
textChunkLimit | Maximum 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
// Simple text chunking
function chunkByParagraph(text: string, limit: number): string[] {
// Split by paragraphs
// Respect limit per chunk
// Avoid splitting mid-sentence
}Markdown Chunking
// 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
| Channel | Text Limit | Chunking Mode |
|---|---|---|
| Telegram | 4000 chars | markdown |
| Discord | 2000 chars | markdown |
| Varies | text | |
| Slack | Varies | markdown |
| Signal | Varies | text |
| iMessage | Varies | text |
Idempotency
Source: src/gateway/server-methods/send.ts
Message sending supports idempotency deduplication to prevent duplicate messages caused by network retries:
// 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():
// 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
idempotencyKeycaching to prevent duplicate messages - CLI dependencies are connected to the infrastructure layer through a bridging pattern
Next: Agent Overview