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:
| Mode | Resolution |
|---|
shared | One session per messaging group (ignores thread) |
per-thread | One session per (messaging group, thread) pair |
agent-shared | One session per agent group (all messaging groups share) |
Database schema
Central database (data/v2.db)
| Table | Purpose |
|---|
agent_groups | Agent workspaces |
container_configs | Per-agent-group runtime config (provider, model, packages, MCP servers, mounts) |
messaging_groups | Platform chats/channels |
messaging_group_agents | Wirings with engage/scope/session config |
users | Namespaced platform identifiers |
user_roles | Owner and admin roles |
agent_group_members | Unprivileged membership |
user_dms | Cached DM channel mapping |
sessions | Session status and container tracking |
pending_questions | Interactive question cards |
pending_sender_approvals | Unknown sender approval flow |
Session databases
Each session has two databases in data/v2-sessions/{agent_group_id}/{session_id}/:
inbound.db (host writes):
| Table | Purpose |
|---|
messages_in | Inbound messages, tasks, system notifications |
delivered | Delivery tracking |
destinations | Live destination map |
session_routing | Default reply routing |
outbound.db (container writes):
| Table | Purpose |
|---|
messages_out | Outbound messages |
processing_ack | Processing acknowledgments |
session_state | Persistent key/value store |
container_state | Tool-in-flight tracking |
Channel approval flow
When a message arrives on an unwired channel:
- Router detects no wirings exist for this messaging group
- Channel-request gate sends approval card to the owner
- 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
- Deny — sets
denied_at on the messaging group
Sender approval flow
When an unknown sender messages on a request_approval channel:
- Approval card sent to the designated approver
- Approve — adds sender to
agent_group_members, replays original message
- Deny — deletes pending row (future messages re-trigger)