Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
87 changes: 83 additions & 4 deletions advanced/container-runtime.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@
| Health check | `docker info` | `container system status` |
| Platform | macOS, Linux, Windows (WSL2) | macOS 15+ only |

### Switching runtimes

Check warning on line 48 in advanced/container-runtime.mdx

View check run for this annotation

Mintlify / Mintlify Validation (qwibitai-nanoclaw-8) - vale-spellcheck

advanced/container-runtime.mdx#L48

Did you really mean 'runtimes'?

Run the `/convert-to-apple-container` skill in Claude Code. To revert, use `git revert`.

Expand All @@ -57,15 +57,15 @@
- **Bun** (pinned to 1.3.12) — runs agent-runner TypeScript directly (no compilation)
- **Chromium** — browser automation via agent-browser
- **Claude Code SDK** — `@anthropic-ai/claude-code` installed globally via pnpm
- **tini** — PID 1 signal forwarding (ensures outbound.db writes finalize on SIGTERM)

Check warning on line 60 in advanced/container-runtime.mdx

View check run for this annotation

Mintlify / Mintlify Validation (qwibitai-nanoclaw-8) - vale-spellcheck

advanced/container-runtime.mdx#L60

Did you really mean 'tini'?
- **pnpm** (via corepack) — for global Node CLI installs

Check warning on line 61 in advanced/container-runtime.mdx

View check run for this annotation

Mintlify / Mintlify Validation (qwibitai-nanoclaw-8) - vale-spellcheck

advanced/container-runtime.mdx#L61

Did you really mean 'corepack'?
- **System tools** — `curl`, `git`, `ca-certificates`, `unzip`
- **Optional CJK fonts** — `fonts-noto-cjk` (~200 MB, opt-in via `INSTALL_CJK_FONTS=true`)

### Key design decisions

- **Source is NOT baked in** — `/app/src` is a read-only bind mount from the host. Source changes never require an image rebuild.
- **`only-built-dependencies` allowlist** in `.npmrc` for `agent-browser` and `@anthropic-ai/claude-code`

Check warning on line 68 in advanced/container-runtime.mdx

View check run for this annotation

Mintlify / Mintlify Validation (qwibitai-nanoclaw-8) - vale-spellcheck

advanced/container-runtime.mdx#L68

Did you really mean 'allowlist'?
- **Runs as `node` user** (non-root) with `/workspace/group` as working directory
- **Entrypoint**: `tini -> entrypoint.sh -> exec bun run /app/src/index.ts`

Expand All @@ -77,11 +77,90 @@

### Per-agent-group images

Agent groups can specify custom packages in `container.json`. The host builds a derived Docker image:
Agent groups can specify custom packages in their container config (stored in the `container_configs` DB table). The host builds a derived Docker image:

- Tag: derived from the checkout-scoped base image and agent group
- Built on top of `nanoclaw-agent-v2-<slug>:latest`
- Adds custom apt and npm packages
- Resulting image tag is written back to the `container_configs.image_tag` column

## Container configuration storage

Per-agent-group runtime config lives in the `container_configs` table in the central DB. The legacy `groups/<folder>/container.json` file is now a **materialized view** — written by the host at spawn time, read by the container at startup. The container has no idea the DB exists; nothing inside the container changed.

### Schema

The `container_configs` table has one row per agent group, with both scalar and JSON columns:

| Column | Type | Purpose |
|---|---|---|
| `agent_group_id` | TEXT (PK, FK) | References `agent_groups.id` (cascades on delete) |
| `provider` | TEXT | Agent provider override (`claude`, `opencode`, etc.) |
| `model` | TEXT | Model name (e.g., `claude-sonnet-4-6`) |
| `effort` | TEXT | Reasoning effort hint |
| `image_tag` | TEXT | Persisted Docker image tag for per-group builds |
| `assistant_name` | TEXT | Display name in system prompt |
| `max_messages_per_prompt` | INTEGER | Override for `MAX_MESSAGES_PER_PROMPT` |
| `skills` | TEXT (JSON) | `"all"` or `["skill1", ...]` |
| `mcp_servers` | TEXT (JSON) | `Record<string, McpServerConfig>` |
| `packages_apt` | TEXT (JSON) | `string[]` of apt packages |
| `packages_npm` | TEXT (JSON) | `string[]` of npm packages |
| `additional_mounts` | TEXT (JSON) | `AdditionalMountConfig[]` |
| `cli_scope` | TEXT | `disabled` \| `group` (default) \| `global` — controls in-container `ncl` access |
| `updated_at` | TEXT | ISO timestamp |

### Backfill

On startup, the host runs a one-time backfill that seeds `container_configs` rows from any existing `groups/<folder>/container.json` files (and the legacy `agent_groups.agent_provider` column). The backfill is idempotent — it skips groups that already have a row.

### Provider cascade

Provider resolution simplified from a 3-step cascade to 2 steps:

```
sessions.agent_provider
→ container_configs.provider
→ 'claude'
```

The legacy `agent_groups.agent_provider` column is retained for backwards compat but no longer participates in resolution and is no longer exposed via `ncl groups`. Configure provider via `ncl groups config update --id <group-id> --provider <name>`.

Check warning on line 126 in advanced/container-runtime.mdx

View check run for this annotation

Mintlify / Mintlify Validation (qwibitai-nanoclaw-8) - vale-spellcheck

advanced/container-runtime.mdx#L126

Did you really mean 'compat'?

### Updating config

All writes go through the DB layer. Multi-word verbs accept either spaces (preferred) or dashes — `ncl groups config get` and `ncl groups config-get` both work.

- **`ncl groups config update`** — change scalar fields (`--provider`, `--model`, `--effort`, `--image-tag`, `--assistant-name`, `--max-messages-per-prompt`, `--cli-scope`)
- **`ncl groups config add-mcp-server`** / **`config remove-mcp-server`** — manage MCP servers
- **`ncl groups config add-package`** / **`config remove-package`** — manage apt/npm packages
- **Self-mod approvals** (`install_packages`, `add_mcp_server`) — write to DB instead of file
- **`ncl groups config get`** — view current config (open access; others require approval)

Config CLI ops only write to the DB; restart is now a separate command — see "Explicit restart" below. The host helper `restartAgentGroupContainers()` is invoked by self-mod approvals to apply config changes.

### CLI scope

`cli_scope` controls what an in-container agent can do via `ncl`:

| Value | Behavior |
|---|---|
| `disabled` | Agent never learns about `ncl` (the instructions section is excluded from the composed `CLAUDE.md`). The host CLI dispatcher rejects any `cli_request`. |
| `group` (default) | Agent can call `groups`, `sessions`, `destinations`, `members` only, scoped to its own agent group. `--id`, `--agent_group_id`, and `--group` args are auto-filled. Cross-group reads are rejected post-handler; help output reflects the scope. `cli_scope` arg is blocked outright. |

Check warning on line 147 in advanced/container-runtime.mdx

View check run for this annotation

Mintlify / Mintlify Validation (qwibitai-nanoclaw-8) - vale-spellcheck

advanced/container-runtime.mdx#L147

Did you really mean 'arg'?
| `global` | Unrestricted — current behavior. Set automatically for owner agent groups by `init-first-agent`. |

Enforcement is host-side only — no image rebuild or env var needed.

### Explicit restart

```bash
ncl groups restart --id <group-id> [--rebuild] [--message <text>]
```

- Kills running containers for the agent group; without `--message`, they come back on the next inbound message
- With `--message`, an `on_wake` row is written to `inbound.db` and the container respawns immediately via the `onExit` callback

Check warning on line 159 in advanced/container-runtime.mdx

View check run for this annotation

Mintlify / Mintlify Validation (qwibitai-nanoclaw-8) - vale-spellcheck

advanced/container-runtime.mdx#L159

Did you really mean 'respawns'?
- `--rebuild` forces an image rebuild before respawn (useful after package changes); package commands no longer trigger a build implicitly

Check warning on line 160 in advanced/container-runtime.mdx

View check run for this annotation

Mintlify / Mintlify Validation (qwibitai-nanoclaw-8) - vale-spellcheck

advanced/container-runtime.mdx#L160

Did you really mean 'respawn'?
- Called from inside a container, `--id` is auto-filled and only the calling session is restarted

The `on_wake` flag on `messages_in` ensures wake messages are delivered only on the new container's first poll iteration. Without it, the dying container — still in its SIGTERM grace window — could steal the message before exiting. `killContainer` accepts an `onExit` callback that fires after the process actually exits, guaranteeing race-free respawn.

Check warning on line 163 in advanced/container-runtime.mdx

View check run for this annotation

Mintlify / Mintlify Validation (qwibitai-nanoclaw-8) - vale-spellcheck

advanced/container-runtime.mdx#L163

Did you really mean 'respawn'?

## Two-database IO model

Expand All @@ -94,7 +173,7 @@
| `messages_in` | Inbound messages, tasks, system notifications |
| `delivered` | Tracks delivery outcomes for outbound message IDs |
| `destinations` | Live destination map (channels and other agents) |
| `session_routing` | Default reply routing (channel_type, platform_id, thread_id) |

Check warning on line 176 in advanced/container-runtime.mdx

View check run for this annotation

Mintlify / Mintlify Validation (qwibitai-nanoclaw-8) - vale-spellcheck

advanced/container-runtime.mdx#L176

Did you really mean 'channel_type'?

Check warning on line 176 in advanced/container-runtime.mdx

View check run for this annotation

Mintlify / Mintlify Validation (qwibitai-nanoclaw-8) - vale-spellcheck

advanced/container-runtime.mdx#L176

Did you really mean 'platform_id'?

Check warning on line 176 in advanced/container-runtime.mdx

View check run for this annotation

Mintlify / Mintlify Validation (qwibitai-nanoclaw-8) - vale-spellcheck

advanced/container-runtime.mdx#L176

Did you really mean 'thread_id'?

### outbound.db (container writes, host reads)

Expand All @@ -109,7 +188,7 @@

Three invariants are critical for correctness:

1. **`journal_mode=DELETE`** — WAL's mmapped `-shm` doesn't refresh across Docker mounts

Check warning on line 191 in advanced/container-runtime.mdx

View check run for this annotation

Mintlify / Mintlify Validation (qwibitai-nanoclaw-8) - vale-spellcheck

advanced/container-runtime.mdx#L191

Did you really mean 'WAL's'?

Check warning on line 191 in advanced/container-runtime.mdx

View check run for this annotation

Mintlify / Mintlify Validation (qwibitai-nanoclaw-8) - vale-spellcheck

advanced/container-runtime.mdx#L191

Did you really mean 'mmapped'?
2. **Host opens-writes-closes per operation** — closing invalidates the container's page cache
3. **One writer per file** — DELETE-mode journal unlink isn't atomic across the mount

Expand All @@ -117,11 +196,11 @@

### Spawning containers

Containers are spawned by the `spawnContainer` function. Wake calls are deduplicated via an in-flight promise map.

Check warning on line 199 in advanced/container-runtime.mdx

View check run for this annotation

Mintlify / Mintlify Validation (qwibitai-nanoclaw-8) - vale-spellcheck

advanced/container-runtime.mdx#L199

Did you really mean 'deduplicated'?

<Steps>
<Step title="Read agent group config">
The host reads `container.json` and resolves provider contributions.
<Step title="Materialize container config">
The host reads the agent group's row from the `container_configs` DB table and writes it as `groups/<folder>/container.json`. This file is a materialized view — the DB is the source of truth, and the file is regenerated on every spawn so the runner always sees fresh config. Provider contributions are resolved from this config.
</Step>

<Step title="Build volume mounts">
Expand Down Expand Up @@ -151,13 +230,13 @@
|------|---------------|------|---------|
| Session folder | `/workspace` | RW | inbound.db, outbound.db, outbox/, inbox/ |
| Agent group folder | `/workspace/agent` | RW | Working files |
| container.json | `/workspace/agent/container.json` | RO | Nested read-only config |
| container.json | `/workspace/agent/container.json` | RO | Materialized from DB at spawn time |
| Composed CLAUDE.md | `/workspace/agent/CLAUDE.md` | RO | Regenerated each spawn |
| Global memory | `/workspace/global` | RO | Shared instructions |
| Agent-runner source | `/app/src` | RO | Bind mount from host |
| Container skills | `/app/skills` | RO | Shared skill definitions |
| Claude SDK state | `/home/node/.claude` | RW | SDK state + skill symlinks |
| Additional mounts | `/workspace/extra/{name}` | Per-config | Validated against allowlist |

Check warning on line 239 in advanced/container-runtime.mdx

View check run for this annotation

Mintlify / Mintlify Validation (qwibitai-nanoclaw-8) - vale-spellcheck

advanced/container-runtime.mdx#L239

Did you really mean 'allowlist'?
| Provider mounts | Various | Per-provider | Provider-contributed |

### Timeouts and stale detection
Expand All @@ -165,7 +244,7 @@
Containers have two timeout/detection mechanisms:

1. **Container timeout** — maximum runtime before force kill (default: 30 minutes)
2. **Stale detection** — host sweep checks `.heartbeat` mtime and `processing_ack` age to detect stuck containers

Check warning on line 247 in advanced/container-runtime.mdx

View check run for this annotation

Mintlify / Mintlify Validation (qwibitai-nanoclaw-8) - vale-spellcheck

advanced/container-runtime.mdx#L247

Did you really mean 'mtime'?

### Container shutdown

Expand Down Expand Up @@ -204,7 +283,7 @@

<Accordion title="Inspect container mounts">
```bash
docker inspect nanoclaw-{session-id} | jq '.[0].Mounts'

Check warning on line 286 in advanced/container-runtime.mdx

View check run for this annotation

Mintlify / Mintlify Validation (qwibitai-nanoclaw-8) - vale-spellcheck

advanced/container-runtime.mdx#L286

Did you really mean 'jq'?
```
</Accordion>

Expand Down
33 changes: 18 additions & 15 deletions advanced/troubleshooting.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -133,21 +133,24 @@

### Solutions

<Accordion title="Increase timeout for specific group">
Modify the group's `containerConfig` in the database:
<Accordion title="Inspect or change a group's container config">
Container config lives in the `container_configs` table in `data/v2.db`. Inspect with the CLI:

```bash
sqlite3 store/messages.db
ncl groups config get --id <agent-group-id>
```

```sql
UPDATE registered_groups
SET container_config = json_set(
COALESCE(container_config, '{}'),
'$.timeout',
3600000 -- 1 hour in milliseconds
)
WHERE name = 'Family Chat';

Update scalar fields (e.g., model, effort, image tag, max messages per prompt). Config writes only touch the DB — to apply the change to a running container, follow up with `ncl groups restart`:

```bash
ncl groups config update --id <agent-group-id> --model claude-sonnet-4-6 --max-messages-per-prompt 20
ncl groups restart --id <agent-group-id>
```

Or query directly with SQLite:

```bash
sqlite3 data/v2.db "SELECT * FROM container_configs WHERE agent_group_id = '<id>';"
```
</Accordion>

Expand Down Expand Up @@ -189,7 +192,7 @@

### Cause

By default, systemd sets `Linger=no` for users. When your last SSH session closes, systemd terminates all user-level processes — including the NanoClaw service. This is especially common on cloud VMs (EC2, GCP, Oracle Cloud).

Check warning on line 195 in advanced/troubleshooting.mdx

View check run for this annotation

Mintlify / Mintlify Validation (qwibitai-nanoclaw-8) - vale-spellcheck

advanced/troubleshooting.mdx#L195

Did you really mean 'systemd'?

Check warning on line 195 in advanced/troubleshooting.mdx

View check run for this annotation

Mintlify / Mintlify Validation (qwibitai-nanoclaw-8) - vale-spellcheck

advanced/troubleshooting.mdx#L195

Did you really mean 'systemd'?

Check warning on line 195 in advanced/troubleshooting.mdx

View check run for this annotation

Mintlify / Mintlify Validation (qwibitai-nanoclaw-8) - vale-spellcheck

advanced/troubleshooting.mdx#L195

Did you really mean 'VMs'?

### Solution

Expand All @@ -207,7 +210,7 @@
```

<Note>
If `loginctl enable-linger` fails with a permission error, your system may require polkit authorization. Contact your system administrator or run the command with `sudo`.

Check warning on line 213 in advanced/troubleshooting.mdx

View check run for this annotation

Mintlify / Mintlify Validation (qwibitai-nanoclaw-8) - vale-spellcheck

advanced/troubleshooting.mdx#L213

Did you really mean 'polkit'?
</Note>

## Known issues
Expand All @@ -233,7 +236,7 @@

**Symptoms:** `Container exited with code 125: pull access denied for nanoclaw-agent` — the container image disappears after a few hours, even though you just built it.

**Cause:** If your container runtime has Kubernetes enabled (Rancher Desktop enables it by default), the kubelet runs image garbage collection when disk usage exceeds 85%. NanoClaw containers are ephemeral (run and exit), so `nanoclaw-agent:latest` is never protected by a running container. The kubelet sees it as unused and deletes it — often overnight when no messages are being processed.

Check warning on line 239 in advanced/troubleshooting.mdx

View check run for this annotation

Mintlify / Mintlify Validation (qwibitai-nanoclaw-8) - vale-spellcheck

advanced/troubleshooting.mdx#L239

Did you really mean 'kubelet'?

Check warning on line 239 in advanced/troubleshooting.mdx

View check run for this annotation

Mintlify / Mintlify Validation (qwibitai-nanoclaw-8) - vale-spellcheck

advanced/troubleshooting.mdx#L239

Did you really mean 'kubelet'?

**Fix:** Disable Kubernetes if you don't need it:

Expand All @@ -256,7 +259,7 @@
grep -E "image found|image NOT found|image missing" logs/nanoclaw.log
```

If you need Kubernetes enabled, set `CONTAINER_IMAGE` to an image stored in a registry that the kubelet won't garbage-collect, or raise the kubelet's GC thresholds.

Check warning on line 262 in advanced/troubleshooting.mdx

View check run for this annotation

Mintlify / Mintlify Validation (qwibitai-nanoclaw-8) - vale-spellcheck

advanced/troubleshooting.mdx#L262

Did you really mean 'kubelet'?

Check warning on line 262 in advanced/troubleshooting.mdx

View check run for this annotation

Mintlify / Mintlify Validation (qwibitai-nanoclaw-8) - vale-spellcheck

advanced/troubleshooting.mdx#L262

Did you really mean 'kubelet's'?

### Resume branches from stale tree position

Expand Down Expand Up @@ -304,7 +307,7 @@

<CodeGroup>
```bash macOS
launchctl kickstart -k gui/$(id -u)/com.nanoclaw

Check warning on line 310 in advanced/troubleshooting.mdx

View check run for this annotation

Mintlify / Mintlify Validation (qwibitai-nanoclaw-8) - vale-spellcheck

advanced/troubleshooting.mdx#L310

Did you really mean 'launchctl'?
```

```bash Linux
Expand Down Expand Up @@ -346,7 +349,7 @@

<CodeGroup>
```bash macOS
launchctl kickstart -k gui/$(id -u)/com.nanoclaw

Check warning on line 352 in advanced/troubleshooting.mdx

View check run for this annotation

Mintlify / Mintlify Validation (qwibitai-nanoclaw-8) - vale-spellcheck

advanced/troubleshooting.mdx#L352

Did you really mean 'launchctl'?
```

```bash Linux
Expand All @@ -370,8 +373,8 @@
# Verify the mount allowlist is readable
cat ~/.config/nanoclaw/mount-allowlist.json

# Check group's container_config in DB
sqlite3 store/messages.db "SELECT name, container_config FROM registered_groups;"
# Check a group's container config (mounts and packages live here)
sqlite3 data/v2.db "SELECT agent_group_id, additional_mounts, packages_apt, packages_npm FROM container_configs;"
```

### Solutions
Expand All @@ -393,21 +396,21 @@
}
```

Then restart the service. If the mount allowlist file did not exist at startup, NanoClaw will detect it once created without requiring a restart.

Check warning on line 399 in advanced/troubleshooting.mdx

View check run for this annotation

Mintlify / Mintlify Validation (qwibitai-nanoclaw-8) - vale-spellcheck

advanced/troubleshooting.mdx#L399

Did you really mean 'allowlist'?
</Accordion>

<Accordion title="Reset mount allowlist to defaults">
If you need to regenerate the default mount allowlist (for example, after a misconfiguration), re-run setup with the `--force` flag:

Check warning on line 403 in advanced/troubleshooting.mdx

View check run for this annotation

Mintlify / Mintlify Validation (qwibitai-nanoclaw-8) - vale-spellcheck

advanced/troubleshooting.mdx#L403

Did you really mean 'allowlist'?

Check warning on line 403 in advanced/troubleshooting.mdx

View check run for this annotation

Mintlify / Mintlify Validation (qwibitai-nanoclaw-8) - vale-spellcheck

advanced/troubleshooting.mdx#L403

Did you really mean 'misconfiguration'?

```bash
claude /setup --force
```

Without `--force`, setup skips the mount allowlist if it already exists to preserve your customizations.

Check warning on line 409 in advanced/troubleshooting.mdx

View check run for this annotation

Mintlify / Mintlify Validation (qwibitai-nanoclaw-8) - vale-spellcheck

advanced/troubleshooting.mdx#L409

Did you really mean 'allowlist'?
</Accordion>

<Accordion title="Check for symlinks">
The mount security system resolves symlinks before validation. If you're mounting a symlink, ensure the resolved path is in the allowlist:

Check warning on line 413 in advanced/troubleshooting.mdx

View check run for this annotation

Mintlify / Mintlify Validation (qwibitai-nanoclaw-8) - vale-spellcheck

advanced/troubleshooting.mdx#L413

Did you really mean 'allowlist'?

```bash
readlink -f /path/to/symlink
Expand All @@ -415,7 +418,7 @@
</Accordion>

<Accordion title="Verify file permissions">
Containers run as the host user (or uid 1000 on some systems). Ensure the host user can read the mounted directories:

Check warning on line 421 in advanced/troubleshooting.mdx

View check run for this annotation

Mintlify / Mintlify Validation (qwibitai-nanoclaw-8) - vale-spellcheck

advanced/troubleshooting.mdx#L421

Did you really mean 'uid'?

```bash
ls -la /path/to/mount
Expand Down Expand Up @@ -493,7 +496,7 @@

| Artifact | Retention | Notes |
|----------|-----------|-------|
| Session JSONLs and tool results | 7 days | Active sessions (from DB) are never deleted |

Check warning on line 499 in advanced/troubleshooting.mdx

View check run for this annotation

Mintlify / Mintlify Validation (qwibitai-nanoclaw-8) - vale-spellcheck

advanced/troubleshooting.mdx#L499

Did you really mean 'JSONLs'?
| Debug logs | 3 days | Active sessions skipped |
| Todo files | 3 days | Active sessions skipped |
| Telemetry | 7 days | Active sessions skipped |
Expand All @@ -509,7 +512,7 @@

## Stale session auto-recovery

NanoClaw automatically detects and recovers from stale or corrupt sessions. When a container fails because the session transcript file (`.jsonl`) is missing — due to a crash mid-write, manual deletion, or disk-full condition — NanoClaw clears the broken session ID and lets the existing backoff mechanism in the group queue retry with a fresh session.

Check warning on line 515 in advanced/troubleshooting.mdx

View check run for this annotation

Mintlify / Mintlify Validation (qwibitai-nanoclaw-8) - vale-spellcheck

advanced/troubleshooting.mdx#L515

Did you really mean 'backoff'?

Stale sessions are detected by matching the error output against known patterns (`no conversation found`, `ENOENT` on `.jsonl` files, or `session not found`). Only these specific signals trigger session clearing — transient errors (network, API) fall through to the normal retry path.

Expand Down
2 changes: 1 addition & 1 deletion api/configuration.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,11 @@
tag: "UPDATED"
---

NanoClaw configuration is managed through environment variables, the `.env` file, and the `src/config.ts` module. In v2, some configuration has moved to `container.json` per agent group.
NanoClaw configuration is managed through environment variables, the `.env` file, and the `src/config.ts` module. In v2, per-agent-group configuration (provider, model, packages, MCP servers, mounts) lives in the `container_configs` table in the central DB and is materialized to `groups/<folder>/container.json` at spawn time. See [Container runtime](/advanced/container-runtime#container-configuration-storage) for details.

## Environment variables

Configuration is read from `.env` file or `process.env`, with hardcoded fallbacks.

Check warning on line 11 in api/configuration.mdx

View check run for this annotation

Mintlify / Mintlify Validation (qwibitai-nanoclaw-8) - vale-spellcheck

api/configuration.mdx#L11

Did you really mean 'hardcoded'?

<ParamField path="ASSISTANT_NAME" type="string" default="Andy">
Name of the assistant. Used in trigger pattern and message routing.
Expand Down Expand Up @@ -55,7 +55,7 @@
</ParamField>

<ParamField path="TZ" type="string" default="system timezone">
Timezone for scheduled tasks (cron expressions). Resolved from `TZ` env, `.env` file, then system default. Validated as a real IANA timezone identifier. Falls back to `UTC` if no valid timezone is found.

Check warning on line 58 in api/configuration.mdx

View check run for this annotation

Mintlify / Mintlify Validation (qwibitai-nanoclaw-8) - vale-spellcheck

api/configuration.mdx#L58

Did you really mean 'cron'?
</ParamField>

## Timezone configuration
Expand All @@ -67,7 +67,7 @@
3. `Intl.DateTimeFormat().resolvedOptions().timeZone` (system default)
4. `'UTC'` (fallback)

Each candidate is validated as a real IANA timezone identifier before being accepted. This affects cron expression evaluation for scheduled tasks.

Check warning on line 70 in api/configuration.mdx

View check run for this annotation

Mintlify / Mintlify Validation (qwibitai-nanoclaw-8) - vale-spellcheck

api/configuration.mdx#L70

Did you really mean 'cron'?

## Directory paths

Expand All @@ -90,7 +90,7 @@
</ResponseField>

<ResponseField name="MOUNT_ALLOWLIST_PATH" type="string">
`~/.config/nanoclaw/mount-allowlist.json` — mount security allowlist (never mounted into containers)

Check warning on line 93 in api/configuration.mdx

View check run for this annotation

Mintlify / Mintlify Validation (qwibitai-nanoclaw-8) - vale-spellcheck

api/configuration.mdx#L93

Did you really mean 'allowlist'?
</ResponseField>

## Trigger pattern
Expand Down Expand Up @@ -135,5 +135,5 @@
- **Secrets** are never read by NanoClaw directly — OneCLI manages them externally
- The OneCLI Agent Vault injects credentials into container API traffic at request time
- Containers cannot extract real credentials from the vault
- Mount allowlist is stored outside the project root and never mounted into containers

Check warning on line 138 in api/configuration.mdx

View check run for this annotation

Mintlify / Mintlify Validation (qwibitai-nanoclaw-8) - vale-spellcheck

api/configuration.mdx#L138

Did you really mean 'allowlist'?
- The `.env` file is read by the config module for NanoClaw settings only (not for API keys)
38 changes: 36 additions & 2 deletions api/group-management.mdx
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
---
title: Group management
description: API reference for agent groups, messaging groups, wirings, and the v2 entity model

Check warning on line 3 in api/group-management.mdx

View check run for this annotation

Mintlify / Mintlify Validation (qwibitai-nanoclaw-8) - vale-spellcheck

api/group-management.mdx#L3

Did you really mean 'wirings'?
tag: "UPDATED"
---

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`.

Check warning on line 7 in api/group-management.mdx

View check run for this annotation

Mintlify / Mintlify Validation (qwibitai-nanoclaw-8) - vale-spellcheck

api/group-management.mdx#L7

Did you really mean 'wirings'?

## Entity model

Expand All @@ -17,14 +17,16 @@
id: string; // Unique identifier
name: string; // Display name
folder: string; // Filesystem folder name
agent_provider?: string; // Optional provider override
/** @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 on disk (`container.json`), not in the database
- 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

Expand All @@ -45,9 +47,40 @@
- Auto-created on first mention or DM
- `denied_at` silently drops future mentions

### Container configs

Check warning on line 50 in api/group-management.mdx

View check run for this annotation

Mintlify / Mintlify Validation (qwibitai-nanoclaw-8) - vale-spellcheck

api/group-management.mdx#L50

Did you really mean 'configs'?

Per-agent-group runtime config:

```typescript
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](/advanced/container-runtime#container-configuration-storage) 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)

Check warning on line 81 in api/group-management.mdx

View check run for this annotation

Mintlify / Mintlify Validation (qwibitai-nanoclaw-8) - vale-spellcheck

api/group-management.mdx#L81

Did you really mean 'Wirings'?

Check warning on line 81 in api/group-management.mdx

View check run for this annotation

Mintlify / Mintlify Validation (qwibitai-nanoclaw-8) - vale-spellcheck

api/group-management.mdx#L81

Did you really mean 'messaging_group_agents'?

Wirings connect messaging groups to agent groups:

Check warning on line 83 in api/group-management.mdx

View check run for this annotation

Mintlify / Mintlify Validation (qwibitai-nanoclaw-8) - vale-spellcheck

api/group-management.mdx#L83

Did you really mean 'Wirings'?

```typescript
interface MessagingGroupAgent {
Expand Down Expand Up @@ -128,9 +161,10 @@
| 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 |

Check warning on line 166 in api/group-management.mdx

View check run for this annotation

Mintlify / Mintlify Validation (qwibitai-nanoclaw-8) - vale-spellcheck

api/group-management.mdx#L166

Did you really mean 'Wirings'?
| `users` | Namespaced platform identifiers |

Check warning on line 167 in api/group-management.mdx

View check run for this annotation

Mintlify / Mintlify Validation (qwibitai-nanoclaw-8) - vale-spellcheck

api/group-management.mdx#L167

Did you really mean 'Namespaced'?
| `user_roles` | Owner and admin roles |
| `agent_group_members` | Unprivileged membership |
| `user_dms` | Cached DM channel mapping |
Expand Down Expand Up @@ -162,13 +196,13 @@

## Channel approval flow

When a message arrives on an unwired channel:

Check warning on line 199 in api/group-management.mdx

View check run for this annotation

Mintlify / Mintlify Validation (qwibitai-nanoclaw-8) - vale-spellcheck

api/group-management.mdx#L199

Did you really mean 'unwired'?

1. Router detects no wirings exist for this messaging group

Check warning on line 201 in api/group-management.mdx

View check run for this annotation

Mintlify / Mintlify Validation (qwibitai-nanoclaw-8) - vale-spellcheck

api/group-management.mdx#L201

Did you really mean 'wirings'?
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)

Check warning on line 205 in api/group-management.mdx

View check run for this annotation

Mintlify / Mintlify Validation (qwibitai-nanoclaw-8) - vale-spellcheck

api/group-management.mdx#L205

Did you really mean 'DMs'?
- Triggering sender is auto-admitted as a member
- Original event is replayed
4. **Deny** — sets `denied_at` on the messaging group
Expand Down
3 changes: 2 additions & 1 deletion concepts/architecture.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
keywords: ["architecture", "system design", "single process"]
---

NanoClaw is a lightweight AI assistant that runs agents in isolated containers. The v2 architecture is a ground-up rewrite built on a two-database session model, a pluggable module system, and a new entity model that separates users, agent groups, messaging groups, and wirings.

Check warning on line 8 in concepts/architecture.mdx

View check run for this annotation

Mintlify / Mintlify Validation (qwibitai-nanoclaw-8) - vale-spellcheck

concepts/architecture.mdx#L8

Did you really mean 'pluggable'?

Check warning on line 8 in concepts/architecture.mdx

View check run for this annotation

Mintlify / Mintlify Validation (qwibitai-nanoclaw-8) - vale-spellcheck

concepts/architecture.mdx#L8

Did you really mean 'wirings'?

## High-level overview

Expand Down Expand Up @@ -41,7 +41,7 @@

### Channel adapters

NanoClaw uses a self-registering adapter pattern for messaging channels. Installed adapters such as Telegram, Discord, WhatsApp, Signal, Slack, Teams, iMessage, and the local CLI register at startup. Channels with missing credentials are skipped with a warning.

Check warning on line 44 in concepts/architecture.mdx

View check run for this annotation

Mintlify / Mintlify Validation (qwibitai-nanoclaw-8) - vale-spellcheck

concepts/architecture.mdx#L44

Did you really mean 'iMessage'?

All adapters implement a common interface with `onInbound`, `onInboundEvent`, `onMetadata`, and `onAction` callbacks, allowing the rest of the system to be channel-agnostic.

Expand All @@ -49,16 +49,16 @@

The router (`src/router.ts`) is the central message routing pipeline:

1. **Thread policy** — non-threaded adapters (Telegram, WhatsApp, iMessage) collapse `threadId` to null

Check warning on line 52 in concepts/architecture.mdx

View check run for this annotation

Mintlify / Mintlify Validation (qwibitai-nanoclaw-8) - vale-spellcheck

concepts/architecture.mdx#L52

Did you really mean 'iMessage'?
2. **Messaging group lookup** — finds or auto-creates messaging groups on mentions/DMs
3. **Unwired channel handling** — if no agents are wired, the channel-request gate escalates to the owner for approval

Check warning on line 54 in concepts/architecture.mdx

View check run for this annotation

Mintlify / Mintlify Validation (qwibitai-nanoclaw-8) - vale-spellcheck

concepts/architecture.mdx#L54

Did you really mean 'Unwired'?
4. **Sender resolution** — the permissions module extracts namespaced user IDs and upserts users

Check warning on line 55 in concepts/architecture.mdx

View check run for this annotation

Mintlify / Mintlify Validation (qwibitai-nanoclaw-8) - vale-spellcheck

concepts/architecture.mdx#L55

Did you really mean 'namespaced'?

Check warning on line 55 in concepts/architecture.mdx

View check run for this annotation

Mintlify / Mintlify Validation (qwibitai-nanoclaw-8) - vale-spellcheck

concepts/architecture.mdx#L55

Did you really mean 'upserts'?
5. **Fan-out** — each wired agent is evaluated independently against engage mode, sender scope, and access gates
6. **Engage evaluation** — per-agent decision based on `pattern`, `mention`, or `mention-sticky` mode
7. **Delivery** — engaging agents get a session wake and container spawn; non-engaging agents with `accumulate` policy store the message for future context

<Info>
Message IDs are namespaced by agent group ID to prevent collisions during fan-out to multiple agents.

Check warning on line 61 in concepts/architecture.mdx

View check run for this annotation

Mintlify / Mintlify Validation (qwibitai-nanoclaw-8) - vale-spellcheck

concepts/architecture.mdx#L61

Did you really mean 'namespaced'?
</Info>

### Session manager
Expand All @@ -77,7 +77,7 @@

The container runner (`src/container-runner.ts`) spawns and manages isolated agent execution:

- **Wake deduplication** — concurrent wake calls for the same session are deduplicated via an in-flight promise map

Check warning on line 80 in concepts/architecture.mdx

View check run for this annotation

Mintlify / Mintlify Validation (qwibitai-nanoclaw-8) - vale-spellcheck

concepts/architecture.mdx#L80

Did you really mean 'deduplication'?

Check warning on line 80 in concepts/architecture.mdx

View check run for this annotation

Mintlify / Mintlify Validation (qwibitai-nanoclaw-8) - vale-spellcheck

concepts/architecture.mdx#L80

Did you really mean 'deduplicated'?
- **Per-agent-group images** — custom Docker images with additional apt/npm packages can be built per agent group
- **Shared source** — `/app/src` is a read-only bind mount from the host; source changes never require an image rebuild
- **No stdin/stdout** — all IO is via the two-DB split; no stdin piping or output markers
Expand All @@ -92,7 +92,7 @@
- **Retry** — 3 attempts per message, then permanently failed

<Note>
Deduplication via an in-flight set prevents double-delivery when both polls hit the same session simultaneously.

Check warning on line 95 in concepts/architecture.mdx

View check run for this annotation

Mintlify / Mintlify Validation (qwibitai-nanoclaw-8) - vale-spellcheck

concepts/architecture.mdx#L95

Did you really mean 'Deduplication'?
</Note>

### Host sweep
Expand All @@ -100,7 +100,7 @@
The host sweep (`src/host-sweep.ts`) runs every 60 seconds:

- Syncs `processing_ack` from `outbound.db` to update inbound message status
- Detects stale containers via heartbeat mtime and processing_ack age

Check warning on line 103 in concepts/architecture.mdx

View check run for this annotation

Mintlify / Mintlify Validation (qwibitai-nanoclaw-8) - vale-spellcheck

concepts/architecture.mdx#L103

Did you really mean 'mtime'?

Check warning on line 103 in concepts/architecture.mdx

View check run for this annotation

Mintlify / Mintlify Validation (qwibitai-nanoclaw-8) - vale-spellcheck

concepts/architecture.mdx#L103

Did you really mean 'processing_ack'?
- Wakes containers for due messages (scheduled tasks with `process_after <= now`)
- Advances recurring task series (creates next-occurrence rows)

Expand All @@ -108,26 +108,27 @@

**Central database** (`data/v2.db`) stores the entity model:

- **agent_groups** — workspaces with folder, name, and optional provider
- **agent_groups** — workspaces with folder and name (provider moved to `container_configs.provider`)

Check warning on line 111 in concepts/architecture.mdx

View check run for this annotation

Mintlify / Mintlify Validation (qwibitai-nanoclaw-8) - vale-spellcheck

concepts/architecture.mdx#L111

Did you really mean 'agent_groups'?
- **container_configs** — per-agent-group runtime config (provider, model, effort, packages, MCP servers, additional mounts); materialized to `groups/<folder>/container.json` at spawn time

Check warning on line 112 in concepts/architecture.mdx

View check run for this annotation

Mintlify / Mintlify Validation (qwibitai-nanoclaw-8) - vale-spellcheck

concepts/architecture.mdx#L112

Did you really mean 'container_configs'?
- **messaging_groups** — platform chats with `unknown_sender_policy` (`strict`, `request_approval`, or `public`)

Check warning on line 113 in concepts/architecture.mdx

View check run for this annotation

Mintlify / Mintlify Validation (qwibitai-nanoclaw-8) - vale-spellcheck

concepts/architecture.mdx#L113

Did you really mean 'messaging_groups'?
- **messaging_group_agents** — many-to-many wirings with engage mode, pattern, sender scope, ignored message policy, session mode, and priority

Check warning on line 114 in concepts/architecture.mdx

View check run for this annotation

Mintlify / Mintlify Validation (qwibitai-nanoclaw-8) - vale-spellcheck

concepts/architecture.mdx#L114

Did you really mean 'messaging_group_agents'?

Check warning on line 114 in concepts/architecture.mdx

View check run for this annotation

Mintlify / Mintlify Validation (qwibitai-nanoclaw-8) - vale-spellcheck

concepts/architecture.mdx#L114

Did you really mean 'wirings'?
- **users** — namespaced platform identifiers (e.g., `phone:+1555...`, `tg:123`, `discord:456`)

Check warning on line 115 in concepts/architecture.mdx

View check run for this annotation

Mintlify / Mintlify Validation (qwibitai-nanoclaw-8) - vale-spellcheck

concepts/architecture.mdx#L115

Did you really mean 'namespaced'?
- **user_roles** — owner (always global) or admin (global or scoped to agent group)

Check warning on line 116 in concepts/architecture.mdx

View check run for this annotation

Mintlify / Mintlify Validation (qwibitai-nanoclaw-8) - vale-spellcheck

concepts/architecture.mdx#L116

Did you really mean 'user_roles'?
- **sessions** — status tracking for both session and container

**Session inbound.db** (host writes, container reads):

- **messages_in** — inbound messages with status, `process_after`, recurrence, `series_id`, and trigger flag

Check warning on line 121 in concepts/architecture.mdx

View check run for this annotation

Mintlify / Mintlify Validation (qwibitai-nanoclaw-8) - vale-spellcheck

concepts/architecture.mdx#L121

Did you really mean 'messages_in'?
- **delivered** — tracks delivery outcomes
- **destinations** — live destination map (channels and other agents)
- **session_routing** — default reply routing

Check warning on line 124 in concepts/architecture.mdx

View check run for this annotation

Mintlify / Mintlify Validation (qwibitai-nanoclaw-8) - vale-spellcheck

concepts/architecture.mdx#L124

Did you really mean 'session_routing'?

**Session outbound.db** (container writes, host reads):

- **messages_out** — outbound messages with `deliver_after` and recurrence

Check warning on line 128 in concepts/architecture.mdx

View check run for this annotation

Mintlify / Mintlify Validation (qwibitai-nanoclaw-8) - vale-spellcheck

concepts/architecture.mdx#L128

Did you really mean 'messages_out'?
- **processing_ack** — tracks which inbound messages the container has processed

Check warning on line 129 in concepts/architecture.mdx

View check run for this annotation

Mintlify / Mintlify Validation (qwibitai-nanoclaw-8) - vale-spellcheck

concepts/architecture.mdx#L129

Did you really mean 'processing_ack'?
- **session_state** — persistent key/value store (e.g., SDK session ID for resume)

Check warning on line 130 in concepts/architecture.mdx

View check run for this annotation

Mintlify / Mintlify Validation (qwibitai-nanoclaw-8) - vale-spellcheck

concepts/architecture.mdx#L130

Did you really mean 'session_state'?
- **container_state** — tool-in-flight state for stuck-detection

Check warning on line 131 in concepts/architecture.mdx

View check run for this annotation

Mintlify / Mintlify Validation (qwibitai-nanoclaw-8) - vale-spellcheck

concepts/architecture.mdx#L131

Did you really mean 'container_state'?

### Module system

Expand Down Expand Up @@ -231,7 +232,7 @@
- **Tools**: `agent-browser`, `vercel` CLI, `curl`, `git`
- **SDK**: `@anthropic-ai/claude-code` (Claude Agent SDK)
- **PID 1**: `tini` for proper signal forwarding
- **User**: Runs as `node` user (uid 1000, non-root)

Check warning on line 235 in concepts/architecture.mdx

View check run for this annotation

Mintlify / Mintlify Validation (qwibitai-nanoclaw-8) - vale-spellcheck

concepts/architecture.mdx#L235

Did you really mean 'uid'?
- **Working directory**: `/workspace/group`

<Info>
Expand All @@ -240,12 +241,12 @@

## Startup sequence

1. **Central DB initialization** — opens `data/v2.db`, runs migrations, performs one-time filesystem cutover

Check warning on line 244 in concepts/architecture.mdx

View check run for this annotation

Mintlify / Mintlify Validation (qwibitai-nanoclaw-8) - vale-spellcheck

concepts/architecture.mdx#L244

Did you really mean 'cutover'?
2. **Container runtime check** — ensures Docker is running, cleans up orphan containers
3. **Channel adapter initialization** — registers all channel adapters with `onInbound`, `onInboundEvent`, `onMetadata`, and `onAction` callbacks
4. **Delivery adapter bridge** — bridges the delivery system to channel adapters for `deliver()` and `setTyping()` calls
5. **Delivery polls** — starts active poll (1s for running containers) and sweep poll (60s for all active sessions)
6. **Host sweep** — 60s sweep for processing_ack sync, stale detection, due-message wake, and recurrence advancement

Check warning on line 249 in concepts/architecture.mdx

View check run for this annotation

Mintlify / Mintlify Validation (qwibitai-nanoclaw-8) - vale-spellcheck

concepts/architecture.mdx#L249

Did you really mean 'processing_ack'?

## Graceful shutdown

Expand Down
23 changes: 18 additions & 5 deletions concepts/containers.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
tag: "UPDATED"
---

NanoClaw runs all agents inside containers (lightweight Linux VMs) to provide true OS-level isolation. This is the primary security boundary that makes Bash access and code execution safe.

Check warning on line 7 in concepts/containers.mdx

View check run for this annotation

Mintlify / Mintlify Validation (qwibitai-nanoclaw-8) - vale-spellcheck

concepts/containers.mdx#L7

Did you really mean 'VMs'?

## Why containers?

Expand Down Expand Up @@ -34,7 +34,7 @@
- **Tools**: `agent-browser` for browser automation, `vercel` CLI, `curl`, `git`
- **SDK**: `@anthropic-ai/claude-code` installed globally via pnpm
- **PID 1**: `tini` for proper signal forwarding so `outbound.db` writes finalize on SIGTERM
- **User**: `node` (uid 1000, non-root)

Check warning on line 37 in concepts/containers.mdx

View check run for this annotation

Mintlify / Mintlify Validation (qwibitai-nanoclaw-8) - vale-spellcheck

concepts/containers.mdx#L37

Did you really mean 'uid'?
- **Working directory**: `/workspace/group`

<Note>
Expand All @@ -45,7 +45,7 @@

The entrypoint uses `tini` for signal forwarding:

1. **tini** starts as PID 1 (forwards signals cleanly)

Check warning on line 48 in concepts/containers.mdx

View check run for this annotation

Mintlify / Mintlify Validation (qwibitai-nanoclaw-8) - vale-spellcheck

concepts/containers.mdx#L48

Did you really mean 'tini'?
2. **entrypoint.sh** runs setup scripts
3. **Bun executes agent-runner**: `exec bun run /app/src/index.ts`
4. Agent-runner polls `inbound.db` for messages and writes responses to `outbound.db`
Expand Down Expand Up @@ -74,23 +74,23 @@
|-------|---------------|------|---------|
| Session folder | `/workspace` | Read-write | `inbound.db`, `outbound.db`, `outbox/`, `.claude/` |
| Agent group folder | `/workspace/agent` | Read-write | Working files, `CLAUDE.local.md` |
| Container config | `/workspace/agent/container.json` | Read-only | Nested RO mount (agent can't modify config) |
| Container config | `/workspace/agent/container.json` | Read-only | Materialized from DB at spawn time (agent can't modify) |
| Composed CLAUDE.md | `/workspace/agent/CLAUDE.md` | Read-only | Regenerated each spawn |
| CLAUDE.md fragments | `/workspace/agent/.claude-fragments` | Read-only | Fragment files for composition |
| Global memory | `/workspace/global` | Read-only | `groups/global/` directory |
| Shared CLAUDE.md | `/app/CLAUDE.md` | Read-only | Base CLAUDE.md |
| Agent-runner source | `/app/src` | Read-only | Shared source (bind mount from host) |
| Container skills | `/app/skills` | Read-only | Shared skill definitions |
| Claude SDK state | `/home/node/.claude` | Read-write | SDK state + skill symlinks |
| Additional mounts | `/workspace/extra/{name}` | Per-config | From `container.json` (validated against allowlist) |
| Additional mounts | `/workspace/extra/{name}` | Per-config | From container config (validated against allowlist) |

Check warning on line 85 in concepts/containers.mdx

View check run for this annotation

Mintlify / Mintlify Validation (qwibitai-nanoclaw-8) - vale-spellcheck

concepts/containers.mdx#L85

Did you really mean 'allowlist'?

<Warning>
The `container.json` file is mounted read-only as a nested mount inside the read-write agent group folder. This prevents the agent from modifying its own container configuration.
The `container.json` file is a **materialized view** of the DB-backed `container_configs` row. The host writes it at spawn time and mounts it read-only as a nested mount inside the read-write agent group folder. The agent cannot modify its own container configuration — config changes go through self-mod approvals or `ncl groups config <verb>` commands.
</Warning>

### Mount security

All additional mounts are validated against the allowlist at `~/.config/nanoclaw/mount-allowlist.json`:

Check warning on line 93 in concepts/containers.mdx

View check run for this annotation

Mintlify / Mintlify Validation (qwibitai-nanoclaw-8) - vale-spellcheck

concepts/containers.mdx#L93

Did you really mean 'allowlist'?

```json
{
Expand All @@ -101,7 +101,7 @@
"description": "Development projects"
}
],
"blockedPatterns": ["password", "secret", "token"]

Check warning on line 104 in concepts/containers.mdx

View check run for this annotation

Mintlify / Mintlify Validation (qwibitai-nanoclaw-8) - vale-spellcheck

concepts/containers.mdx#L104

Did you really mean 'blockedPatterns'?
}
```

Expand Down Expand Up @@ -142,18 +142,31 @@
- **Host-initiated**: `docker stop` sends SIGTERM; `tini` forwards to Bun process
- **Stale detection**: host sweep detects containers with old heartbeats or stuck processing_ack
- **Fallback**: SIGKILL if graceful stop fails
- **`onExit` callback**: `killContainer` accepts an optional callback that fires after the process actually exits, used by the restart flow to guarantee the old container is gone before the new one spawns

<Info>
Even if the container crashes, all data in session databases and mounted directories persists. Only the container process itself is ephemeral.
</Info>

### Explicit restart

`ncl groups restart --id <group-id> [--rebuild] [--message <text>]` kills running containers for the agent group and lets them respawn:

- Without `--message`, containers come back on the next inbound message
- With `--message`, an `on_wake` message is queued in `inbound.db` and the container respawns immediately via the `onExit` callback
- `--rebuild` forces an image rebuild before respawn (useful after package changes)
- Called from inside a container, `--id` is auto-filled and only the calling session is restarted

The `on_wake` flag on `messages_in` ensures wake messages are delivered only on the fresh container's first poll iteration. This prevents the dying container — still in its SIGTERM grace window — from stealing the message before it exits.

## Per-agent-group images

Agent groups can specify custom packages in `container.json`. The host builds a derived Docker image with additional apt and npm packages:
Agent groups can specify custom packages in their container config (the `container_configs` DB table — materialized to `container.json` at spawn time). The host builds a derived Docker image with additional apt and npm packages:

- Image tag: derived from the checkout-scoped base image and agent group
- Built on top of the base `nanoclaw-agent-v2-<slug>:latest` image
- Cached — only rebuilt when package lists change
- Image tag is persisted to the DB after build

## Timeouts

Expand Down Expand Up @@ -187,7 +200,7 @@
- `schedule_task`, `cancel_task`, `pause_task`, `resume_task`, `update_task` — task management
- `list_tasks` — view scheduled tasks

Additional MCP servers can be configured in `container.json`.
Additional MCP servers can be configured per-agent-group via `ncl groups config add-mcp-server` (writes to the `container_configs` DB table).

### Global memory injection

Expand All @@ -206,7 +219,7 @@
- **Headless**: always (no display in container)
- **User data**: stored in group folder (persists across runs)
- **Network**: full access (same as host, no restrictions)
- **Optional CJK fonts**: install via `INSTALL_CJK_FONTS=true` build arg (~200 MB)

Check warning on line 222 in concepts/containers.mdx

View check run for this annotation

Mintlify / Mintlify Validation (qwibitai-nanoclaw-8) - vale-spellcheck

concepts/containers.mdx#L222

Did you really mean 'arg'?

## Security implications

Expand All @@ -219,13 +232,13 @@

### What containers DON'T protect against

- **Network access** — agents have full network access (can exfiltrate data)

Check warning on line 235 in concepts/containers.mdx

View check run for this annotation

Mintlify / Mintlify Validation (qwibitai-nanoclaw-8) - vale-spellcheck

concepts/containers.mdx#L235

Did you really mean 'exfiltrate'?
- **Mounted directory tampering** — agents can modify anything in mounted read-write directories
- **Vault-based API access** — containers can make authenticated API requests through the OneCLI vault (though they cannot extract real credentials)
- **Resource exhaustion** — no CPU/memory limits enforced (can DoS host)

<Warning>
Containers provide filesystem isolation, not network isolation. Agents can make arbitrary HTTP requests and exfiltrate data over the network.

Check warning on line 241 in concepts/containers.mdx

View check run for this annotation

Mintlify / Mintlify Validation (qwibitai-nanoclaw-8) - vale-spellcheck

concepts/containers.mdx#L241

Did you really mean 'exfiltrate'?
</Warning>

## Troubleshooting
Expand All @@ -246,7 +259,7 @@

1. Check mount paths are readable by host user
2. Check uid/gid mapping
3. Verify allowlist includes path (for additional mounts)

Check warning on line 262 in concepts/containers.mdx

View check run for this annotation

Mintlify / Mintlify Validation (qwibitai-nanoclaw-8) - vale-spellcheck

concepts/containers.mdx#L262

Did you really mean 'allowlist'?
4. Check symlink resolution didn't change path

## Related topics
Expand Down
5 changes: 2 additions & 3 deletions concepts/groups.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
tag: "UPDATED"
---

In v2, NanoClaw uses a new entity model that separates **agent groups** (workspaces where agents run) from **messaging groups** (platform chats and channels). These are connected through **wirings** — many-to-many relationships that control how messages are routed to agents.

Check warning on line 7 in concepts/groups.mdx

View check run for this annotation

Mintlify / Mintlify Validation (qwibitai-nanoclaw-8) - vale-spellcheck

concepts/groups.mdx#L7

Did you really mean 'wirings'?

## Entity model

Expand All @@ -13,8 +13,7 @@
An agent group is:

- A workspace with its own folder under `groups/{name}/`
- An optional provider configuration
- A container configuration (`container.json`) with custom packages and mounts
- A row in the `container_configs` DB table with provider, model, effort, packages, MCP servers, and additional mounts
- The unit of credential scoping (each gets its own OneCLI agent)

### Messaging groups
Expand All @@ -26,9 +25,9 @@
- Can be denied (sets `denied_at` to silently drop future mentions)
- Auto-created on first mention or DM

### Wirings (messaging_group_agents)

Check warning on line 28 in concepts/groups.mdx

View check run for this annotation

Mintlify / Mintlify Validation (qwibitai-nanoclaw-8) - vale-spellcheck

concepts/groups.mdx#L28

Did you really mean 'Wirings'?

Check warning on line 28 in concepts/groups.mdx

View check run for this annotation

Mintlify / Mintlify Validation (qwibitai-nanoclaw-8) - vale-spellcheck

concepts/groups.mdx#L28

Did you really mean 'messaging_group_agents'?

Wirings connect messaging groups to agent groups with four orthogonal axes:

Check warning on line 30 in concepts/groups.mdx

View check run for this annotation

Mintlify / Mintlify Validation (qwibitai-nanoclaw-8) - vale-spellcheck

concepts/groups.mdx#L30

Did you really mean 'Wirings'?

| Axis | Options | Purpose |
|------|---------|---------|
Expand Down Expand Up @@ -92,7 +91,7 @@

Three critical invariants ensure correctness across Docker mount boundaries:

1. **`journal_mode=DELETE`** — WAL's mmapped `-shm` file doesn't refresh host-to-guest

Check warning on line 94 in concepts/groups.mdx

View check run for this annotation

Mintlify / Mintlify Validation (qwibitai-nanoclaw-8) - vale-spellcheck

concepts/groups.mdx#L94

Did you really mean 'WAL's'?

Check warning on line 94 in concepts/groups.mdx

View check run for this annotation

Mintlify / Mintlify Validation (qwibitai-nanoclaw-8) - vale-spellcheck

concepts/groups.mdx#L94

Did you really mean 'mmapped'?
2. **Host opens-writes-closes per operation** — closing invalidates the container's page cache
3. **One writer per file** — DELETE-mode journal unlink isn't atomic across the mount

Expand All @@ -100,9 +99,9 @@

### User model

Users are identified by namespaced platform identifiers:

Check warning on line 102 in concepts/groups.mdx

View check run for this annotation

Mintlify / Mintlify Validation (qwibitai-nanoclaw-8) - vale-spellcheck

concepts/groups.mdx#L102

Did you really mean 'namespaced'?

- `phone:+15551234567` (WhatsApp, iMessage)

Check warning on line 104 in concepts/groups.mdx

View check run for this annotation

Mintlify / Mintlify Validation (qwibitai-nanoclaw-8) - vale-spellcheck

concepts/groups.mdx#L104

Did you really mean 'iMessage'?
- `tg:123456789` (Telegram)
- `discord:123456789` (Discord)
- `email:user@example.com` (Gmail)
Expand All @@ -121,10 +120,10 @@

## Channel approval

When a message arrives on an unwired channel (no agent wirings exist), the channel-request gate escalates to the owner:

Check warning on line 123 in concepts/groups.mdx

View check run for this annotation

Mintlify / Mintlify Validation (qwibitai-nanoclaw-8) - vale-spellcheck

concepts/groups.mdx#L123

Did you really mean 'unwired'?

Check warning on line 123 in concepts/groups.mdx

View check run for this annotation

Mintlify / Mintlify Validation (qwibitai-nanoclaw-8) - vale-spellcheck

concepts/groups.mdx#L123

Did you really mean 'wirings'?

1. Owner receives an approval card
2. **Approve** — creates a wiring with defaults (`mention-sticky` for groups, `pattern='.'` for DMs), admits the triggering sender, replays the original event

Check warning on line 126 in concepts/groups.mdx

View check run for this annotation

Mintlify / Mintlify Validation (qwibitai-nanoclaw-8) - vale-spellcheck

concepts/groups.mdx#L126

Did you really mean 'DMs'?
3. **Deny** — sets `denied_at` on the messaging group; future mentions drop silently

## Global memory
Expand All @@ -137,7 +136,7 @@

## Additional mounts

Agent groups can have extra directories mounted via `container.json`:
Agent groups can have extra directories mounted via the `additional_mounts` JSON column on their `container_configs` row. The materialized `container.json` mounted into the container looks like this:

```json
{
Expand Down Expand Up @@ -169,7 +168,7 @@
Container concurrency is managed globally:

- **Max concurrent containers**: 5 by default (`MAX_CONCURRENT_CONTAINERS`)
- **Wake deduplication**: concurrent wake calls for the same session share a single in-flight promise

Check warning on line 171 in concepts/groups.mdx

View check run for this annotation

Mintlify / Mintlify Validation (qwibitai-nanoclaw-8) - vale-spellcheck

concepts/groups.mdx#L171

Did you really mean 'deduplication'?
- **Delivery polls**: active poll (1s) for running containers, sweep poll (60s) for all sessions

## Best practices
Expand Down
2 changes: 1 addition & 1 deletion features/customization.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
1. **Code changes** — for behavior baked into the orchestrator (trigger pattern, polling, timeouts)
2. **Wiring settings** — per-messaging-group config stored in the central DB (engage mode, sender scope, session mode)
3. **Skills** — for features and integrations (install via `/add-<name>`, uninstall via `git revert`)
4. **Mount allowlist** — for which host directories an agent can see

Check warning on line 13 in features/customization.mdx

View check run for this annotation

Mintlify / Mintlify Validation (qwibitai-nanoclaw-8) - vale-spellcheck

features/customization.mdx#L13

Did you really mean 'allowlist'?

## Philosophy

Expand Down Expand Up @@ -99,7 +99,7 @@
MAX_CONCURRENT_CONTAINERS=10 systemctl --user restart nanoclaw
```

Or persist in your systemd / launchd service definition.

Check warning on line 102 in features/customization.mdx

View check run for this annotation

Mintlify / Mintlify Validation (qwibitai-nanoclaw-8) - vale-spellcheck

features/customization.mdx#L102

Did you really mean 'systemd'?

Check warning on line 102 in features/customization.mdx

View check run for this annotation

Mintlify / Mintlify Validation (qwibitai-nanoclaw-8) - vale-spellcheck

features/customization.mdx#L102

Did you really mean 'launchd'?

## Per-wiring behavior (engage mode, sender scope, session mode)

Expand Down Expand Up @@ -150,9 +150,9 @@

`CLAUDE.md` is composed at session start — a shared base plus the per-group fragment. Per-group changes don't affect other agent groups.

## Mount allowlist

Check warning on line 153 in features/customization.mdx

View check run for this annotation

Mintlify / Mintlify Validation (qwibitai-nanoclaw-8) - vale-spellcheck

features/customization.mdx#L153

Did you really mean 'allowlist'?

Agents only see what you mount. The allowlist lives at `~/.config/nanoclaw/mount-allowlist.json` (outside the project root, never mounted into containers):

Check warning on line 155 in features/customization.mdx

View check run for this annotation

Mintlify / Mintlify Validation (qwibitai-nanoclaw-8) - vale-spellcheck

features/customization.mdx#L155

Did you really mean 'allowlist'?

```json
{
Expand All @@ -168,12 +168,12 @@
"description": "Development project"
}
],
"blockedPatterns": [],

Check warning on line 171 in features/customization.mdx

View check run for this annotation

Mintlify / Mintlify Validation (qwibitai-nanoclaw-8) - vale-spellcheck

features/customization.mdx#L171

Did you really mean 'blockedPatterns'?
"nonMainReadOnly": true
}
```

Per-agent-group mount requests live in `groups/<folder>/container.json`. The host validates each request against the allowlist before mounting. See [Security model](/advanced/security-model) for the full picture.
Per-agent-group mount requests live in the `additional_mounts` JSON column on the `container_configs` row, materialized to `groups/<folder>/container.json` at spawn time. The host validates each request against the allowlist before mounting. See [Security model](/advanced/security-model) for the full picture.

Check warning on line 176 in features/customization.mdx

View check run for this annotation

Mintlify / Mintlify Validation (qwibitai-nanoclaw-8) - vale-spellcheck

features/customization.mdx#L176

Did you really mean 'allowlist'?

## The `/customize` skill

Expand Down Expand Up @@ -235,6 +235,6 @@
## Related

- [Messaging](/features/messaging) — inbound routing and engage evaluation
- [Scheduled tasks](/features/scheduled-tasks) — task model and cron

Check warning on line 238 in features/customization.mdx

View check run for this annotation

Mintlify / Mintlify Validation (qwibitai-nanoclaw-8) - vale-spellcheck

features/customization.mdx#L238

Did you really mean 'cron'?
- [Security](/concepts/security) — sender policies, user roles
- [Architecture](/concepts/architecture) — two-DB session model
Loading