Skip to main content

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.

In v2, NanoClaw uses a new entity model that separates agent groups (workspaces) from messaging groups (platform chats). These are connected through wirings — many-to-many relationships stored in messaging_group_agents.

Entity model

Agent groups

Agent groups are workspaces where agents run:
interface AgentGroup {
  id: string;             // Unique identifier
  name: string;           // Display name
  folder: string;         // Filesystem folder name
  agent_provider?: string; // Optional provider override
  created_at: string;     // ISO timestamp
}
  • Each agent group has a folder under groups/{folder}/
  • Container configuration lives on disk (container.json), not in the database
  • Each gets its own OneCLI agent identifier for credential scoping

Messaging groups

Messaging groups represent platform chats and channels:
interface MessagingGroup {
  id: string;                    // Unique identifier
  channel_type: string;          // e.g., 'whatsapp', 'telegram', 'discord'
  platform_id: string;           // Platform-specific chat ID
  name?: string;                 // Display name
  unknown_sender_policy: UnknownSenderPolicy; // 'strict' | 'request_approval' | 'public'
  denied_at?: string;            // ISO timestamp if denied
}
  • Unique on (channel_type, platform_id)
  • Auto-created on first mention or DM
  • denied_at silently drops future mentions

Wirings (messaging_group_agents)

Wirings connect messaging groups to agent groups:
interface MessagingGroupAgent {
  messaging_group_id: string;
  agent_group_id: string;
  engage_mode: EngageMode;           // 'pattern' | 'mention' | 'mention-sticky'
  engage_pattern: string;            // Regex (e.g., '.' = always match)
  sender_scope: SenderScope;         // 'all' | 'known'
  ignored_message_policy: IgnoredMessagePolicy; // 'drop' | 'accumulate'
  session_mode: SessionMode;         // 'shared' | 'per-thread' | 'agent-shared'
  priority: number;                  // Evaluation order
}

Users and roles

Users

interface User {
  id: string;          // Namespaced: 'channelType:handle'
  kind: string;        // phone, email, discord, telegram, matrix, etc.
  created_at: string;
}

User roles

interface UserRole {
  user_id: string;
  role: 'owner' | 'admin';
  agent_group_id?: string;  // null = global scope
}
  • Owner — always global, full system access
  • Admin — global or scoped to a specific agent group

Agent group members

interface AgentGroupMember {
  user_id: string;
  agent_group_id: string;
}
Members can interact with agents in their assigned group when sender_scope='known' is set on a wiring.

Sessions

interface Session {
  id: string;
  agent_group_id: string;
  messaging_group_id?: string;
  thread_id?: string;
  status: 'active' | 'closed';
  container_status: 'running' | 'idle' | 'stopped';
  last_active: string;
  created_at: string;
}
Session resolution depends on the wiring’s session_mode:
ModeResolution
sharedOne session per messaging group (ignores thread)
per-threadOne session per (messaging group, thread) pair
agent-sharedOne session per agent group (all messaging groups share)

Database schema

Central database (data/v2.db)

TablePurpose
agent_groupsAgent workspaces
messaging_groupsPlatform chats/channels
messaging_group_agentsWirings with engage/scope/session config
usersNamespaced platform identifiers
user_rolesOwner and admin roles
agent_group_membersUnprivileged membership
user_dmsCached DM channel mapping
sessionsSession status and container tracking
pending_questionsInteractive question cards
pending_sender_approvalsUnknown sender approval flow

Session databases

Each session has two databases in data/v2-sessions/{agent_group_id}/{session_id}/: inbound.db (host writes):
TablePurpose
messages_inInbound messages, tasks, system notifications
deliveredDelivery tracking
destinationsLive destination map
session_routingDefault reply routing
outbound.db (container writes):
TablePurpose
messages_outOutbound messages
processing_ackProcessing acknowledgments
session_statePersistent key/value store
container_stateTool-in-flight tracking

Channel approval flow

When a message arrives on an unwired channel:
  1. Router detects no wirings exist for this messaging group
  2. Channel-request gate sends approval card to the owner
  3. Approve — creates wiring with defaults:
    • Groups: mention-sticky engage mode
    • DMs: pattern='.' (always respond)
    • Triggering sender is auto-admitted as a member
    • Original event is replayed
  4. Deny — sets denied_at on the messaging group

Sender approval flow

When an unknown sender messages on a request_approval channel:
  1. Approval card sent to the designated approver
  2. Approve — adds sender to agent_group_members, replays original message
  3. Deny — deletes pending row (future messages re-trigger)
Last modified on April 22, 2026