Skip to main content

Documentation Index

Fetch the complete documentation index at: https://qwibitai-nanoclaw-8-mintlify-container-config-db-1778268498.mintlify.app/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
  /** @deprecated Use container_configs.provider instead. */
  agent_provider: string | null;
  created_at: string;     // ISO timestamp
}
  • Each agent group has a folder under groups/{folder}/
  • Container configuration lives in the container_configs DB table (one row per agent group); the file groups/<folder>/container.json is materialized at spawn time
  • Each gets its own OneCLI agent identifier for credential scoping
  • agent_provider is deprecated and no longer exposed via the CLI — use ncl groups config update --provider to set the provider on the container_configs row instead

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

Container configs

Per-agent-group runtime config:
interface ContainerConfigRow {
  agent_group_id: string;            // PK and FK to agent_groups.id
  provider: string | null;           // 'claude', 'opencode', etc.
  model: string | null;              // Model name (e.g., 'claude-sonnet-4-6')
  effort: string | null;             // Reasoning effort hint
  image_tag: string | null;          // Persisted Docker image tag
  assistant_name: string | null;     // Display name in system prompt
  max_messages_per_prompt: number | null;
  skills: string;                    // JSON: '"all"' | '["skill1","skill2"]'
  mcp_servers: string;               // JSON: Record<string, McpServerConfig>
  packages_apt: string;              // JSON: string[]
  packages_npm: string;              // JSON: string[]
  additional_mounts: string;         // JSON: AdditionalMountConfig[]
  cli_scope: string;                 // 'disabled' | 'group' | 'global' (default 'group')
  updated_at: string;
}
Source of truth in the DB. Materialized to groups/<folder>/container.json at spawn time so the in-container runner can read it from the read-only mount. All writes go through ncl groups config <verb> operations or self-mod approvals — see Container runtime for the full list. cli_scope controls what the in-container agent can do via ncl:
  • disabled — agent never learns about ncl (instructions excluded from CLAUDE.md); host dispatch rejects any cli_request
  • group (default) — agent can call groups, sessions, destinations, members only, scoped to its own agent group; --id and group args are auto-filled and cross-group reads are rejected; cli_scope changes are blocked
  • global — unrestricted; set automatically on the owner’s first agent group via init-first-agent

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
container_configsPer-agent-group runtime config (provider, model, packages, MCP servers, mounts)
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 May 9, 2026