Documentation Index
Fetch the complete documentation index at: https://docs.nanoclaw.dev/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.
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
Messaging group lookup
The router finds or auto-creates a messaging group based on the channel type and platform ID.
Sender resolution
The permissions module extracts a namespaced user ID (e.g.,
tg:123456) and upserts the user record.Fan-out to agents
Each wired agent is evaluated independently against engage mode, sender scope, and access gates.
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 theengage_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-wiringsender_scope controls who can trigger the agent:
| Scope | Behavior |
|---|---|
all | Any user can trigger the agent |
known | Only 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
Messages are formatted for the agent with sender information, timestamps, and metadata. The agent-runner inside the container handles formatting for the configured provider.Channel-aware formatting
This feature requires the
/channel-formatting skill. Apply it with:| Channel | Transformation |
|---|---|
**bold** → *bold*, *italic* → _italic_, headings → bold | |
| Telegram | Same as WhatsApp, but links preserved (Markdown v1) |
| Slack | Same as WhatsApp, but links become <url|text> |
| Discord | Passthrough (Discord renders Markdown) |
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.
Delivery system
The delivery system uses a two-poll architecture:- Active poll (1s) — polls
outbound.dbfor all running-container sessions - Sweep poll (60s) — polls all active sessions (catches messages from exited containers)
- Read due outbound messages from
outbound.db - Filter already-delivered via
inbound.db’sdeliveredtable - Route by kind:
system→ delivery action handlers,agent→ agent-to-agent module, normal → channel adapter - Permission check for cross-channel delivery
- 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):- The router’s channel-request gate sends an approval card to the owner
- Approve — creates a wiring with defaults (
mention-stickyfor groups,pattern='.'for DMs) - Deny — future mentions on this channel are silently dropped
Related documentation
- Scheduled tasks — a scheduled task is a message row with a timestamp
- Integrations overview — channels and providers available as skills
- Customization — modify trigger patterns, engage modes, and delivery polling