CodeDocs Vault

Configuration

Configuration Philosophy

NanoClaw deliberately avoids configuration sprawl. The source code IS the configuration — the codebase is small enough that modifying code is safer than maintaining a complex config system.

There are exactly three categories of configuration:

  1. Environment variables — runtime settings (API keys, trigger word, timezone)
  2. SQLite state — dynamic state managed by the system (groups, sessions, tasks)
  3. External security files — out-of-band security configuration (mount allowlist, sender allowlist)

Environment Variables

File: src/config.ts

All environment configuration is loaded once at startup and exported as constants:

Variable Default Purpose Line
ASSISTANT_NAME "Andy" Trigger word and agent identity config.ts:7
ASSISTANT_HAS_OWN_NUMBER false Whether agent has its own phone number config.ts:10
POLL_INTERVAL 2000 (ms) Message loop polling frequency config.ts:12
SCHEDULER_POLL_INTERVAL 60000 (ms) Task scheduler polling frequency config.ts:13
CONTAINER_IMAGE "nanoclaw-agent:latest" Docker image to spawn config.ts:15
CONTAINER_TIMEOUT 300000 (5 min) Hard timeout for containers config.ts:17
CONTAINER_MAX_OUTPUT_SIZE 10485760 (10 MB) Max stdout/stderr capture config.ts:19
ONECLI_URL "http://host.docker.internal:21666" OneCLI credential gateway URL config.ts:21
MAX_MESSAGES_PER_PROMPT 200 Max messages fetched per prompt config.ts:23
IPC_POLL_INTERVAL 1000 (ms) IPC watcher polling frequency config.ts:25
IDLE_TIMEOUT 1800000 (30 min) Container idle timeout config.ts:27
MAX_CONCURRENT_CONTAINERS 3 Global concurrency limit config.ts:29
TZ / TIMEZONE System timezone Timezone for cron expressions and timestamps config.ts:49

Timezone Resolution (src/config.ts:86-96)

Timezone follows a fallback chain:

TZ env var → NANOCLAW_TIMEZONE env var → system timezone (Intl.DateTimeFormat) → "UTC"

Validated against Intl.DateTimeFormat — invalid timezones fall back to UTC with a warning.

Trigger Pattern (src/config.ts:67-80)

function buildTriggerPattern(trigger: string): RegExp {
  return new RegExp(`^${escapeRegex(trigger)}\\b`, 'i');
}
 
function getTriggerPattern(customTrigger?: string): RegExp {
  if (customTrigger) return buildTriggerPattern(customTrigger);
  return DEFAULT_TRIGGER;  // Built from ASSISTANT_NAME
}

Groups can override the trigger word. The default is @{ASSISTANT_NAME} with word-boundary matching.

Path Configuration

Constant Default Purpose Line
MOUNT_ALLOWLIST_PATH ~/.config/nanoclaw/mount-allowlist.json Mount security allowlist config.ts:33
SENDER_ALLOWLIST_PATH ~/.config/nanoclaw/sender-allowlist.json Sender filtering config config.ts:35
STORE_DIR ./store SQLite database directory config.ts:37
GROUPS_DIR ./groups Per-group persistent storage config.ts:39
DATA_DIR ./data IPC directories, session caches config.ts:41

Security-sensitive files (mount-allowlist.json, sender-allowlist.json) live in ~/.config/nanoclaw/ — outside the project root and outside any container mount.

SQLite State (Dynamic Configuration)

Registered Groups (src/db.ts)

Groups are the primary configurable entity, stored in the registered_groups table:

CREATE TABLE IF NOT EXISTS registered_groups (
  jid TEXT PRIMARY KEY,
  name TEXT NOT NULL,
  folder TEXT NOT NULL UNIQUE,
  trigger TEXT NOT NULL,
  added_at TEXT NOT NULL,
  requires_trigger INTEGER DEFAULT 1,
  is_main INTEGER DEFAULT 0,
  container_config TEXT  -- JSON: { additionalMounts?, timeout? }
)

The container_config JSON field allows per-group customization:

interface ContainerConfig {
  additionalMounts?: AdditionalMount[];  // Extra volume mounts
  timeout?: number;                       // Custom container timeout
}

Sessions (src/db.ts)

CREATE TABLE IF NOT EXISTS sessions (
  group_folder TEXT PRIMARY KEY,
  session_id TEXT NOT NULL
)

Maps each group to its Claude Agent SDK session ID. Sessions persist across container restarts, enabling multi-turn conversations that span days.

Scheduled Tasks (src/db.ts)

CREATE TABLE IF NOT EXISTS scheduled_tasks (
  id TEXT PRIMARY KEY,
  group_folder TEXT NOT NULL,
  chat_jid TEXT NOT NULL,
  prompt TEXT NOT NULL,
  script TEXT,
  schedule_type TEXT NOT NULL,      -- 'cron' | 'interval' | 'once'
  schedule_value TEXT NOT NULL,
  context_mode TEXT DEFAULT 'group', -- 'group' | 'isolated'
  next_run TEXT,
  last_run TEXT,
  last_result TEXT,
  status TEXT DEFAULT 'active',     -- 'active' | 'paused' | 'completed'
  created_at TEXT NOT NULL
)

Security Configuration

Mount Allowlist (~/.config/nanoclaw/mount-allowlist.json)

Controls which host directories can be mounted into containers:

{
  "allowedRoots": [
    {
      "path": "~/projects",
      "allowReadWrite": true,
      "description": "Development projects"
    }
  ],
  "blockedPatterns": ["password", "secret", "token"],
  "nonMainReadOnly": true
}

Key rules:

Sender Allowlist (~/.config/nanoclaw/sender-allowlist.json)

Controls which senders can trigger the agent:

{
  "mode": "trigger",
  "groups": {
    "120363336345536173@g.us": {
      "allowed": ["sender1@s.whatsapp.net"],
      "mode": "drop"
    }
  },
  "logDenied": true
}

Two enforcement modes:

Container-side Configuration

SDK Settings (src/container-runner.ts:128-149)

Each group gets an auto-generated .claude/settings.json:

{
  "env": {
    "CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS": "1",
    "CLAUDE_CODE_ADDITIONAL_DIRECTORIES_CLAUDE_MD": "1",
    "CLAUDE_CODE_DISABLE_AUTO_MEMORY": "0"
  }
}

These enable:

Group CLAUDE.md (groups/{folder}/CLAUDE.md)

Per-group agent instructions. Templates are copied from groups/main/CLAUDE.md or groups/global/CLAUDE.md when a group is registered (src/index.ts:162-179). The assistant name is substituted if different from "Andy".

Container Skills (container/skills/)

Skills are synced from container/skills/ into each group's .claude/skills/ directory at container spawn time (src/container-runner.ts:151-161). This means all groups get the same container skills, but each group's copy is independently writable.

Configuration Diagram

~/.config/nanoclaw/
├── mount-allowlist.json        ← What dirs agents can access (tamper-proof)
└── sender-allowlist.json       ← Who can trigger the agent (tamper-proof)

.env
├── ANTHROPIC_API_KEY           ← API credential (never reaches containers)
├── ASSISTANT_NAME              ← Trigger word
├── TZ                          ← Timezone
└── ...                         ← Other env vars (see table above)

store/
└── messages.db                 ← SQLite: messages, groups, tasks, sessions, state

groups/
├── main/CLAUDE.md              ← Main group agent instructions (template)
├── global/CLAUDE.md            ← Shared instructions (template)
└── {folder}/                   ← Per-group storage
    ├── CLAUDE.md               ← Copied from template on registration
    ├── logs/                   ← Container execution logs
    └── conversations/          ← Archived conversation transcripts

data/
├── ipc/{folder}/               ← Per-group IPC directories
│   ├── messages/               ← Agent → host messages
│   ├── tasks/                  ← Agent → host task commands
│   └── input/                  ← Host → agent follow-up messages
└── sessions/{folder}/          ← Per-group SDK sessions and skills
    ├── .claude/settings.json   ← Auto-generated SDK settings
    ├── .claude/skills/         ← Synced container skills
    └── agent-runner-src/       ← Per-group agent-runner code (customizable)