Skip to main content

Documentation Index

Fetch the complete documentation index at: https://qwibitai-nanoclaw-8-mintlify-explicit-destination-addressin.mintlify.app/llms.txt

Use this file to discover all available pages before exploring further.

The v2 message router handles inbound message evaluation, fan-out to wired agents, and outbound delivery through session databases.

Inbound routing pipeline

The router (src/router.ts) processes inbound messages through these stages:

1. Thread policy

Non-threaded adapters collapse threadId to null:
// Telegram, WhatsApp, iMessage don't support threads
if (!adapter.supportsThreads) {
  message.threadId = null;
}

2. Messaging group lookup

Combined query for messaging group and wired agent count. Messaging groups are auto-created only on mentions or DMs — plain chatter is silent.

3. Unwired channel handling

If no agents are wired and it’s a mention, the channel-request gate escalates to the owner for approval.

4. Sender resolution

The permissions module extracts a namespaced user ID and upserts the users row:
// User ID format: channelType:handle
// Examples: phone:+15551234567, tg:123456, discord:789012

5. Fan-out

Each wired agent is evaluated independently. Message IDs are namespaced by agent group ID to prevent collisions.

6. Engage evaluation

Per-agent decision based on the wiring’s engage_mode:
ModeCondition
patternMessage matches engage_pattern regex ('.' = always)
mentionPlatform-level mention required
mention-stickyPlatform mention OR existing active session

7. Delivery

Engaging agents get a session write and container wake. Non-engaging agents with ignored_message_policy='accumulate' get the message stored with trigger=0.

Module hooks

The router accepts optional pluggable hooks:
HookPurpose
setSenderResolverRuns before agent resolution — extracts user ID
setAccessGateRuns after agent resolution — enforces unknown_sender_policy
setSenderScopeGatePer-wiring sender scope enforcement
setChannelRequestGateEscalation for unwired channels
All hooks are optional. Without the permissions module, the system is allow-all.

Outbound dispatch (in-container)

Before delivery, the agent runner parses the agent’s final text and dispatches each <message to="name">...</message> block to the named destination.
  • Bare text outside <message> blocks is scratchpad — logged but never sent.
  • <internal>...</internal> makes scratchpad intent explicit and is stripped before logging.
  • Wrapping is required, even with a single configured destination. There is no fallback that sends bare text to the originating channel.

Per-destination thread resolution

For each dispatched block, thread_id and in_reply_to are resolved per destination by querying the most recent inbound message that matches the destination’s channel_type and platform_id:
SELECT thread_id, id FROM messages_in
WHERE channel_type = ? AND platform_id = ?
ORDER BY seq DESC LIMIT 1
This matters in agent-shared sessions, where one session serves multiple messaging groups with distinct thread contexts. If the lookup misses, the runner falls back to the session’s inbound routing context.

Outbound delivery

Delivery polls

PollIntervalScope
Active1 secondSessions with running containers
Sweep60 secondsAll active sessions

Delivery pipeline

For each session with due outbound messages:
  1. Read from outbound.db (read-only)
  2. Filter already-delivered via inbound.db’s delivered table
  3. Route by kind:
    • system — dispatch to registered delivery action handlers
    • channel_type='agent' — agent-to-agent module
    • Normal — permission check, then channel adapter delivery
  4. Mark delivered in inbound.db
  5. Clean up outbox/ files (best-effort)

Delivery actions

Modules register handlers via registerDeliveryAction(action, handler):
registerDeliveryAction('schedule_task', handleScheduleTask);
registerDeliveryAction('cancel_task', handleCancelTask);
// etc.

Retry behavior

  • 3 attempts per message
  • Permanently failed after exhausting retries
  • Attempt counter resets on process restart

Types

EngageMode

type EngageMode = 'pattern' | 'mention' | 'mention-sticky';

SenderScope

type SenderScope = 'all' | 'known';

IgnoredMessagePolicy

type IgnoredMessagePolicy = 'drop' | 'accumulate';

SessionMode

type SessionMode = 'shared' | 'per-thread' | 'agent-shared';

MessageInKind

type MessageInKind = 'chat' | 'chat-sdk' | 'task' | 'webhook' | 'system';

MessageInStatus

type MessageInStatus = 'pending' | 'processing' | 'completed' | 'failed';

UnknownSenderPolicy

type UnknownSenderPolicy = 'strict' | 'request_approval' | 'public';
Last modified on May 7, 2026