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 inbox/outbox model

Every agent session has two SQLite files:
  • inbound.db — anything the agent needs to see. User messages, webhooks, scheduled tasks when they come due, agent-to-agent requests. The host writes; the container reads.
  • outbound.db — anything the agent wants to do. Replies, tool calls, schedule requests, sub-agent spawns. The container writes; the host reads.
One writer per file means no SQLite cross-mount contention, no stdin piping, no IPC coordination layer. Every inbound source uses the same messages_in table; every outbound action uses the same messages_out table. Scheduling, channels, and agent-to-agent routing are the same pattern with different metadata.

Message flow

1

Message arrives

A channel adapter receives a message and calls the onInbound callback.
2

Thread policy

Non-threaded adapters (Telegram, WhatsApp, iMessage) collapse threadId to null.
3

Messaging group lookup

The router finds or auto-creates a messaging group based on the channel type and platform ID.
4

Sender resolution

The permissions module extracts a namespaced user ID (e.g., tg:123456) and upserts the user record.
5

Fan-out to agents

Each wired agent is evaluated independently against engage mode, sender scope, and access gates.
6

Session write

For engaging agents, the message is written to inbound.db and the container is woken.
7

Response delivery

The delivery system polls outbound.db and routes responses back through channel adapters.

Engage modes

Engage modes control when an agent responds to messages. Each wiring has its own engage mode:

Pattern mode

The agent responds whenever a message matches the engage_pattern regex:
  • '.' — matches all messages (always respond)
  • '^@Andy\\b' — matches messages starting with @Andy
  • Custom regex — any valid regex pattern

Mention mode

The agent only responds when explicitly mentioned at the platform level (e.g., @bot in Discord, direct reply in WhatsApp).

Mention-sticky mode

The agent responds to a platform mention OR if there’s an existing active session for this agent, messaging group, and thread combination. Once activated by a mention, the agent continues responding in that thread.
Messages that don’t trigger an agent can still be stored if the wiring has ignored_message_policy='accumulate'. These messages provide context when the agent is eventually triggered.

Sender scope

Per-wiring sender_scope controls who can trigger the agent:
ScopeBehavior
allAny user can trigger the agent
knownOnly owner, admin, or group members can trigger
sender_scope='known' provides an additional access layer on top of unknown_sender_policy. Even if a messaging group is public, a wiring with sender_scope='known' restricts its agent to authorized users.

Message formatting

Each inbound message is wrapped in an XML element so the agent can see who sent it, when, and from which destination. The from attribute carries the destination name resolved from the message’s channel and platform ID — the agent uses it to address responses correctly.
KindFormat
chat<message id="..." from="..." sender="..." time="..." reply_to="...">...</message>
task<task from="..." time="...">Instructions: ...</task>
webhook<webhook from="..." source="..." event="...">{...payload...}</webhook>
system<system_response from="..." action="..." status="...">{...result...}</system_response>
If routing fields can’t be matched to a known destination, from falls back to unknown:<channel_type>:<platform_id> so nothing is silently dropped.

Response wrapping

The agent must wrap every response in a <message to="name">...</message> block — this is required even when only one destination is configured. The destinations and required syntax are injected into the system prompt at wake time:
## Sending messages

You can send messages to the following destinations:

- `discord-test`
- `slack-test`

**Every response must be wrapped** in a `<message to="name">...</message>` block.
Multiple <message> blocks can be included in a single response to fan out to several destinations. Text outside of <message> blocks is treated as scratchpad — logged but not sent. Use <internal>...</internal> to make scratchpad intent explicit.

Per-destination thread resolution

In agent-shared sessions where one agent serves multiple messaging groups, each destination may have a different thread context. When dispatching a <message to="name"> block, the agent runner resolves thread_id and in_reply_to from the most recent inbound message matching that destination’s channel and platform — never from a single global routing context. This prevents one channel’s thread from being stamped onto another.

Compaction safety

Claude Code’s PreCompact hook (bun /app/src/compact-instructions.ts) emits custom instructions during context compaction so the summary preserves the routing XML structure (<message from="...">, <task from="...">, <webhook from="...">) and the chronological exchange order. The agent must keep seeing which destination sent each message — otherwise it can’t address replies correctly after a compaction. The hook is added automatically to every agent group’s settings.json on init, and back-filled on existing groups.

Channel-aware formatting

This feature requires the /channel-formatting skill. Apply it with:
git fetch upstream skill/channel-formatting
git merge upstream/skill/channel-formatting
When the channel-formatting skill is applied, outbound messages are automatically converted from Claude’s Markdown output to each channel’s native text syntax.
ChannelTransformation
WhatsApp**bold***bold*, *italic*_italic_, headings → bold
TelegramSame as WhatsApp, but links preserved (Markdown v1)
SlackSame as WhatsApp, but links become <url|text>
DiscordPassthrough (Discord renders Markdown)
Code blocks are always protected — their content is never transformed.

Concurrency

Container concurrency is managed globally:
  • Maximum concurrent containers: 5 by default (MAX_CONCURRENT_CONTAINERS)
  • Wake deduplication: concurrent wake calls for the same session share a single in-flight promise
  • Sessions with running containers are polled every 1 second for outbound messages
  • All active sessions are swept every 60 seconds

Channel routing

Every channel implements the same adapter interface. Chat SDK-backed channels use Vercel’s Chat SDK; native channels keep platform-specific clients behind the same callbacks (onInbound, onInboundEvent, onMetadata, onAction). Routing, fan-out, and delivery do not need platform-specific branches. Optional adapters live on the channels branch or in the current setup flows (Discord, Slack, Telegram, Signal, Teams, Google Chat, WhatsApp, Matrix, iMessage, GitHub, Linear, and more). Install one with /add-<name> or select it during bash nanoclaw.sh. See the integrations overview for the full list.
Channels are installed as skills, not configured through env vars or files. Use /add-telegram, /add-discord, etc. to copy an adapter module into your fork.

Delivery system

The delivery system uses a two-poll architecture:
  • Active poll (1s) — polls outbound.db for all running-container sessions
  • Sweep poll (60s) — polls all active sessions (catches messages from exited containers)
Delivery pipeline per message:
  1. Read due outbound messages from outbound.db
  2. Filter already-delivered via inbound.db’s delivered table
  3. Route by kind: system → delivery action handlers, agent → agent-to-agent module, normal → channel adapter
  4. Permission check for cross-channel delivery
  5. Retry up to 3 times on failure
Typing indicators are paused after each real user-facing delivery to avoid visual flicker.

Channel approval

When a message arrives on an unwired channel (no agent wirings exist):
  1. The router’s channel-request gate sends an approval card to the owner
  2. Approve — creates a wiring with defaults (mention-sticky for groups, pattern='.' for DMs)
  3. Deny — future mentions on this channel are silently dropped
Last modified on May 7, 2026