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
12 changes: 9 additions & 3 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,12 @@

### 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_configs` row (managed via `ncl groups config add-package`/`remove-package`). 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
- Rebuilds are triggered by `ncl groups restart --rebuild` or by the `install_packages` self-mod tool (config writes alone don't restart or rebuild)

## Two-database IO model

Expand All @@ -94,7 +95,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 98 in advanced/container-runtime.mdx

View check run for this annotation

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

advanced/container-runtime.mdx#L98

Did you really mean 'channel_type'?

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

View check run for this annotation

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

advanced/container-runtime.mdx#L98

Did you really mean 'platform_id'?

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

View check run for this annotation

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

advanced/container-runtime.mdx#L98

Did you really mean 'thread_id'?

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

Expand All @@ -109,7 +110,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 113 in advanced/container-runtime.mdx

View check run for this annotation

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

advanced/container-runtime.mdx#L113

Did you really mean 'WAL's'?

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

View check run for this annotation

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

advanced/container-runtime.mdx#L113

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 +118,11 @@

### Spawning containers

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

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

View check run for this annotation

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

advanced/container-runtime.mdx#L121

Did you really mean 'deduplicated'?

<Steps>
<Step title="Read agent group config">
The host reads `container.json` and resolves provider contributions.
The host reads the agent group's `container_configs` row, materializes it to `groups/<folder>/container.json` for the container runner, and resolves provider contributions.
</Step>

<Step title="Build volume mounts">
Expand Down Expand Up @@ -151,13 +152,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 view of the `container_configs` row, regenerated each spawn |
| 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 161 in advanced/container-runtime.mdx

View check run for this annotation

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

advanced/container-runtime.mdx#L161

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

### Timeouts and stale detection
Expand All @@ -165,12 +166,17 @@
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 169 in advanced/container-runtime.mdx

View check run for this annotation

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

advanced/container-runtime.mdx#L169

Did you really mean 'mtime'?

### Container shutdown

- `killContainer(sessionId, reason)` stops the container via `docker stop`, falls back to SIGKILL
- On close/error, the session is marked stopped and typing indicators are cleared
- `killContainer` accepts an optional `onExit` callback that fires after the container process has fully exited, used by the restart flow to guarantee race-free respawn

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

View check run for this annotation

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

advanced/container-runtime.mdx#L175

Did you really mean 'respawn'?

### Restart and on-wake messages

`ncl groups restart` (and the agent-side self-mod handlers) kills running containers for the group and respawns them. When a wake message is supplied, it is written to `messages_in` with `on_wake = 1`, and the container poll loop only delivers `on_wake` rows on its first iteration after spawn. This prevents a dying container — still draining its SIGTERM grace window — from picking up the wake message intended for the fresh process.

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

View check run for this annotation

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

advanced/container-runtime.mdx#L179

Did you really mean 'respawns'?

## Credential injection

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

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

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

View check run for this annotation

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

advanced/container-runtime.mdx#L213

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

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 split between three sources: environment variables and the `.env` file (host process settings), the `src/config.ts` module (defaults and resolution), and the per-agent-group `container_configs` table in the central DB (provider, model, packages, MCP servers, mounts, `cli_scope`). The DB row is the source of truth for per-group container config and is materialized to `groups/<folder>/container.json` at spawn time. Manage it with the `ncl groups config` operations — see [Group management](/api/group-management).

## 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)
19 changes: 18 additions & 1 deletion 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 @@ -23,7 +23,7 @@
```

- Each agent group has a folder under `groups/{folder}/`
- Container configuration lives on disk (`container.json`), not in the database
- Container runtime configuration lives in the `container_configs` table; `groups/{folder}/container.json` is a read-only materialized view written at spawn time
- Each gets its own OneCLI agent identifier for credential scoping

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

### Wirings (messaging_group_agents)

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

View check run for this annotation

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

api/group-management.mdx#L48

Did you really mean 'Wirings'?

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

View check run for this annotation

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

api/group-management.mdx#L48

Did you really mean 'messaging_group_agents'?

Wirings connect messaging groups to agent groups:

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 'Wirings'?

```typescript
interface MessagingGroupAgent {
Expand Down Expand Up @@ -128,9 +128,10 @@
| Table | Purpose |
|-------|---------|
| `agent_groups` | Agent workspaces |
| `container_configs` | Per-agent-group container runtime config (provider, model, packages, MCP servers, mounts, `cli_scope`) |
| `messaging_groups` | Platform chats/channels |
| `messaging_group_agents` | Wirings with engage/scope/session config |

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

View check run for this annotation

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

api/group-management.mdx#L133

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

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

View check run for this annotation

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

api/group-management.mdx#L134

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 +163,13 @@

## Channel approval flow

When a message arrives on an unwired channel:

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 'unwired'?

1. Router detects no wirings exist for this messaging group

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

View check run for this annotation

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

api/group-management.mdx#L168

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 172 in api/group-management.mdx

View check run for this annotation

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

api/group-management.mdx#L172

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 All @@ -180,3 +181,19 @@
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)

## Container config operations

In addition to the standard CRUD verbs, the `groups` resource exposes custom operations for managing the per-group container config and lifecycle:

| Operation | Purpose |
|-----------|---------|
| `ncl groups restart` | Kill and respawn containers for the group. Accepts `--rebuild` to rebuild the per-group image and `--message <text>` to write a wake message that is delivered only to the fresh container's first poll iteration. |

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

View check run for this annotation

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

api/group-management.mdx#L191

Did you really mean 'respawn'?
| `ncl groups config get` | Read the materialized container config for a group. |
| `ncl groups config update` | Patch scalar fields on the `container_configs` row (e.g., `--model`, `--effort`, `--cli-scope`). |
| `ncl groups config add-mcp-server` / `remove-mcp-server` | Wire or remove an MCP server entry. |
| `ncl groups config add-package` / `remove-package` | Add or remove an apt or npm package. |

Config writes do not restart the container — call `ncl groups restart` (with `--rebuild` if package lists changed) to apply changes. From inside a container, `--id` is auto-filled to the calling group, and `restart` only kills the calling session.

The host enforces the `cli_scope` field on every `cli_request`: under `group` scope (the default) only `groups`, `sessions`, `destinations`, and `members` are reachable, group-related arguments are auto-filled, and changing `cli_scope` itself is blocked.
22 changes: 16 additions & 6 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) |
| Materialized container config | `/workspace/agent/container.json` | Read-only | Nested RO mount, regenerated from the DB at spawn time |
| 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 the `container_configs` row (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 inside the container is a read-only materialized view of the `container_configs` row in the central DB. It is regenerated at spawn time and mounted as a nested read-only mount inside the read-write agent group folder, so the agent cannot modify its own container configuration.
</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 @@ -147,14 +147,24 @@
Even if the container crashes, all data in session databases and mounted directories persists. Only the container process itself is ephemeral.
</Info>

## Per-agent-group images
## Per-agent-group container config

Agent groups can specify custom packages in `container.json`. The host builds a derived Docker image with additional apt and npm packages:
Each agent group's container runtime config (provider, model, packages, MCP servers, mounts, CLI scope, etc.) is stored in the `container_configs` table in the central DB and materialized to `groups/<folder>/container.json` at spawn time. The DB row is the source of truth; the file is a read-only view consumed by the container runner.

Configs are managed via the `ncl groups config` custom operations (`get`, `update`, `add-mcp-server`, `remove-mcp-server`, `add-package`, `remove-package`) and by the self-mod MCP tools (`install_packages`, `add_mcp_server`). Agent groups created before this change are migrated automatically by a startup backfill that seeds the table from existing `container.json` files.

### Per-agent-group images

Agent groups can specify custom packages in their config. 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

### Container restart

`ncl groups restart` kills running containers for the agent group and lets them respawn on the next message, with optional `--rebuild` and `--message` flags. When `--message` is provided, a wake message is written with the `on_wake` flag set so it is only delivered to a fresh container's first poll iteration — a dying container in its SIGTERM grace window cannot steal it. From inside a container, `--id` is auto-filled and only the calling session is restarted.

## Timeouts

### Container timeout
Expand Down Expand Up @@ -187,7 +197,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`.

### Global memory injection

Expand All @@ -206,7 +216,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 219 in concepts/containers.mdx

View check run for this annotation

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

concepts/containers.mdx#L219

Did you really mean 'arg'?

## Security implications

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

### What containers DON'T protect against

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

Check warning on line 232 in concepts/containers.mdx

View check run for this annotation

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

concepts/containers.mdx#L232

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 238 in concepts/containers.mdx

View check run for this annotation

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

concepts/containers.mdx#L238

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

## Troubleshooting
Expand All @@ -246,7 +256,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 259 in concepts/containers.mdx

View check run for this annotation

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

concepts/containers.mdx#L259

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

## Related topics
Expand Down
16 changes: 14 additions & 2 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 @@ -14,7 +14,7 @@

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

### Messaging groups
Expand All @@ -26,9 +26,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 29 in concepts/groups.mdx

View check run for this annotation

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

concepts/groups.mdx#L29

Did you really mean 'Wirings'?

Check warning on line 29 in concepts/groups.mdx

View check run for this annotation

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

concepts/groups.mdx#L29

Did you really mean 'messaging_group_agents'?

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

Check warning on line 31 in concepts/groups.mdx

View check run for this annotation

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

concepts/groups.mdx#L31

Did you really mean 'Wirings'?

| Axis | Options | Purpose |
|------|---------|---------|
Expand Down Expand Up @@ -92,7 +92,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 95 in concepts/groups.mdx

View check run for this annotation

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

concepts/groups.mdx#L95

Did you really mean 'WAL's'?

Check warning on line 95 in concepts/groups.mdx

View check run for this annotation

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

concepts/groups.mdx#L95

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 +100,9 @@

### User model

Users are identified by namespaced platform identifiers:

Check warning on line 103 in concepts/groups.mdx

View check run for this annotation

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

concepts/groups.mdx#L103

Did you really mean 'namespaced'?

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

Check warning on line 105 in concepts/groups.mdx

View check run for this annotation

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

concepts/groups.mdx#L105

Did you really mean 'iMessage'?
- `tg:123456789` (Telegram)
- `discord:123456789` (Discord)
- `email:user@example.com` (Gmail)
Expand All @@ -121,10 +121,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 124 in concepts/groups.mdx

View check run for this annotation

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

concepts/groups.mdx#L124

Did you really mean 'unwired'?

Check warning on line 124 in concepts/groups.mdx

View check run for this annotation

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

concepts/groups.mdx#L124

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 127 in concepts/groups.mdx

View check run for this annotation

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

concepts/groups.mdx#L127

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

## Global memory
Expand All @@ -135,9 +135,21 @@
- Injected into the system prompt via `systemPrompt.append`
- Read-only for all agents

## Container CLI scope

Each agent group's `container_configs` row has a `cli_scope` column controlling what the in-container `ncl` admin CLI can do:

| Value | Behavior |
|-------|----------|
| `disabled` | The agent never learns about `ncl` — its instructions are excluded from the composed `CLAUDE.md`, and the host rejects any `cli_request` from this group. |
| `group` (default) | The agent can only access `groups`, `sessions`, `destinations`, and `members`, scoped to its own agent group. `--id`, `--agent-group-id`, and `--group` arguments are auto-filled, and cross-group access is rejected. The `cli_scope` field cannot be changed from inside the container. |
| `global` | Unrestricted. Set automatically for the owner agent group via the first-agent init script. |

Enforcement is host-side only — no image rebuild or env var change is required when the scope is updated.

## Additional mounts

Agent groups can have extra directories mounted via `container.json`:
Agent groups can have extra directories mounted via the `additional_mounts` field of their container config:

```json
{
Expand Down Expand Up @@ -169,7 +181,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 184 in concepts/groups.mdx

View check run for this annotation

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

concepts/groups.mdx#L184

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

## Best practices
Expand Down
16 changes: 14 additions & 2 deletions concepts/security.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -14,19 +14,19 @@
| Owner | Fully trusted | System administrator, all privileges |
| Admins | Trusted | Can approve senders and manage groups (global or scoped) |
| Known users | Permitted | Members of specific agent groups |
| Unknown senders | Untrusted | Policy-controlled per messaging group |

Check warning on line 17 in concepts/security.mdx

View check run for this annotation

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

concepts/security.mdx#L17

Did you really mean 'Untrusted'?
| Container agents | Sandboxed | Isolated execution environment |

Check warning on line 18 in concepts/security.mdx

View check run for this annotation

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

concepts/security.mdx#L18

Did you really mean 'Sandboxed'?
| Incoming messages | User input | Potential prompt injection |

## Security boundaries

### 1. Container isolation (primary boundary)

Agents execute in containers (lightweight Linux VMs), providing:

Check warning on line 25 in concepts/security.mdx

View check run for this annotation

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

concepts/security.mdx#L25

Did you really mean 'VMs'?

- **Process isolation** — container processes cannot affect the host
- **Filesystem isolation** — only explicitly mounted directories are visible
- **Non-root execution** — runs as unprivileged `node` user (uid 1000)

Check warning on line 29 in concepts/security.mdx

View check run for this annotation

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

concepts/security.mdx#L29

Did you really mean 'uid'?
- **Signal forwarding** — `tini` as PID 1 ensures clean shutdown

This is the primary security boundary. Rather than relying on application-level permission checks, the attack surface is limited by what's mounted.
Expand Down Expand Up @@ -69,7 +69,7 @@

### 2. Mount security

#### External allowlist

Check warning on line 72 in concepts/security.mdx

View check run for this annotation

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

concepts/security.mdx#L72

Did you really mean 'allowlist'?

Mount permissions stored at `~/.config/nanoclaw/mount-allowlist.json`, which is:

Expand All @@ -87,7 +87,7 @@
```

<Warning>
The mount allowlist is the security control that prevents agents from accessing sensitive directories. If no allowlist file exists, all additional mounts are blocked by default.

Check warning on line 90 in concepts/security.mdx

View check run for this annotation

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

concepts/security.mdx#L90

Did you really mean 'allowlist'?

Check warning on line 90 in concepts/security.mdx

View check run for this annotation

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

concepts/security.mdx#L90

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

#### Protections
Expand Down Expand Up @@ -128,7 +128,7 @@

#### Channel approval flow

When a message arrives on an unwired channel (no agent wirings exist):

Check warning on line 131 in concepts/security.mdx

View check run for this annotation

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

concepts/security.mdx#L131

Did you really mean 'unwired'?

Check warning on line 131 in concepts/security.mdx

View check run for this annotation

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

concepts/security.mdx#L131

Did you really mean 'wirings'?

1. The channel-request gate sends an approval card to the owner
2. **Approve** — creates a wiring with default settings, admits the triggering sender, replays the original event
Expand Down Expand Up @@ -163,7 +163,19 @@
The OneCLI Agent Vault prevents credential exposure to containers. However, containers can still make authenticated API requests through the vault — they cannot extract the real credentials, but they can use them indirectly.
</Warning>

### 6. Delivery authorization
### 6. In-container CLI scope

Each agent group has a `cli_scope` field on its `container_configs` row controlling what the in-container `ncl` CLI can reach:

| Scope | Behavior |
|-------|----------|
| `disabled` | The agent never learns about `ncl` — its instructions are excluded from `CLAUDE.md`, and host dispatch rejects any `cli_request`. |
| `group` (default) | The agent can only access `groups`, `sessions`, `destinations`, and `members`, restricted to its own agent group. `--id`, `--agent-group-id`, and `--group` are auto-filled. Attempts to escalate `cli_scope` or read other groups' data are blocked at dispatch. |
| `global` | Unrestricted. Reserved for the owner agent group. |

Enforcement is purely host-side, so a compromised container cannot bypass it by tampering with its own materialized `container.json`.

### 7. Delivery authorization

The delivery system enforces per-agent permissions:

Expand All @@ -172,7 +184,7 @@
- Cross-channel delivery requires an explicit `agent_destinations` row
- The `ask_question` message type creates interactive `pending_questions` cards

### 7. Diagnostics and telemetry
### 8. Diagnostics and telemetry

NanoClaw includes opt-in diagnostics that run during `/setup` and `/update-nanoclaw` skill workflows only. There is no runtime telemetry in the application itself.

Expand All @@ -199,7 +211,7 @@
- Agent only has access to its own session's databases
- Cannot deliver to unauthorized channels (delivery authorization)
- Cannot access credential files (not mounted)
- Cannot modify mount allowlist (external, never mounted)

Check warning on line 214 in concepts/security.mdx

View check run for this annotation

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

concepts/security.mdx#L214

Did you really mean 'allowlist'?

**Worst case**: agent sends its own session's messages to attacker (session is compromised, but other sessions remain isolated)

Expand All @@ -225,8 +237,8 @@
## Best practices

1. **Use `request_approval` for public groups** — prevents unauthorized access while allowing legitimate users
2. **Review mount allowlist** — before allowing new mounts, verify they don't contain secrets

Check warning on line 240 in concepts/security.mdx

View check run for this annotation

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

concepts/security.mdx#L240

Did you really mean 'allowlist'?
3. **Use `sender_scope=known` for sensitive wirings** — restricts to authorized users even on public channels

Check warning on line 241 in concepts/security.mdx

View check run for this annotation

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

concepts/security.mdx#L241

Did you really mean 'wirings'?
4. **Set up admin roles** — delegate sender approval without giving full owner access
5. **Monitor delivery logs** — check for unauthorized delivery attempts
6. **Update regularly** — security fixes may be released in upstream NanoClaw updates
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 agent group's `container_configs` row (manage with `ncl groups config update --additional-mounts ...`); they are 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