From cdd3d1d05ae9e12c306ccb555b7583c33452a722 Mon Sep 17 00:00:00 2001 From: "mintlify[bot]" <109931778+mintlify[bot]@users.noreply.github.com> Date: Sat, 9 May 2026 17:30:28 +0000 Subject: [PATCH] Document container_configs DB, on-wake restarts, and CLI scope Generated-By: mintlify-agent --- advanced/container-runtime.mdx | 12 +++++++++--- api/configuration.mdx | 2 +- api/group-management.mdx | 19 ++++++++++++++++++- concepts/containers.mdx | 22 ++++++++++++++++------ concepts/groups.mdx | 16 ++++++++++++++-- concepts/security.mdx | 16 ++++++++++++++-- features/customization.mdx | 2 +- 7 files changed, 73 insertions(+), 16 deletions(-) diff --git a/advanced/container-runtime.mdx b/advanced/container-runtime.mdx index 045e018..291ee31 100644 --- a/advanced/container-runtime.mdx +++ b/advanced/container-runtime.mdx @@ -77,11 +77,12 @@ The agent container is built from `container/Dockerfile` and includes: ### 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-: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 @@ -121,7 +122,7 @@ Containers are spawned by the `spawnContainer` function. Wake calls are deduplic - The host reads `container.json` and resolves provider contributions. + The host reads the agent group's `container_configs` row, materializes it to `groups//container.json` for the container runner, and resolves provider contributions. @@ -151,7 +152,7 @@ Containers are spawned by the `spawnContainer` function. Wake calls are deduplic |------|---------------|------|---------| | 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 | @@ -171,6 +172,11 @@ Containers have two timeout/detection mechanisms: - `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 + +### 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. ## Credential injection diff --git a/api/configuration.mdx b/api/configuration.mdx index 782d8de..8329d96 100644 --- a/api/configuration.mdx +++ b/api/configuration.mdx @@ -4,7 +4,7 @@ description: Environment variables and configuration options for NanoClaw contai 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//container.json` at spawn time. Manage it with the `ncl groups config` operations — see [Group management](/api/group-management). ## Environment variables diff --git a/api/group-management.mdx b/api/group-management.mdx index 0bfaa96..0128d34 100644 --- a/api/group-management.mdx +++ b/api/group-management.mdx @@ -23,7 +23,7 @@ interface AgentGroup { ``` - 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 @@ -128,6 +128,7 @@ Session resolution depends on the wiring's `session_mode`: | 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 | | `users` | Namespaced platform identifiers | @@ -180,3 +181,19 @@ When an unknown sender messages on a `request_approval` channel: 1. Approval card sent to the designated approver 2. **Approve** — adds sender to `agent_group_members`, replays original message 3. **Deny** — deletes pending row (future messages re-trigger) + +## 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 ` to write a wake message that is delivered only to the fresh container's first poll iteration. | +| `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. diff --git a/concepts/containers.mdx b/concepts/containers.mdx index c328ee0..7627e53 100644 --- a/concepts/containers.mdx +++ b/concepts/containers.mdx @@ -74,7 +74,7 @@ Containers only see what's explicitly mounted. The v2 mount structure is differe |-------|---------------|------|---------| | 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 | @@ -82,10 +82,10 @@ Containers only see what's explicitly mounted. The v2 mount structure is differe | 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) | -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. ### Mount security @@ -147,14 +147,24 @@ Container spawning is deduplicated — concurrent wake calls for the same sessio Even if the container crashes, all data in session databases and mounted directories persists. Only the container process itself is ephemeral. -## 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//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-: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 @@ -187,7 +197,7 @@ The `nanoclaw` MCP server provides tools for container-to-host communication via - `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 diff --git a/concepts/groups.mdx b/concepts/groups.mdx index 381153b..5814657 100644 --- a/concepts/groups.mdx +++ b/concepts/groups.mdx @@ -14,7 +14,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 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 @@ -135,9 +135,21 @@ The global memory directory (`groups/global/`) provides shared context: - 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 { diff --git a/concepts/security.mdx b/concepts/security.mdx index 064d9e2..df3c604 100644 --- a/concepts/security.mdx +++ b/concepts/security.mdx @@ -163,7 +163,19 @@ NanoClaw uses the [OneCLI](https://github.com/onecli/onecli) Agent Vault for cen 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. -### 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: @@ -172,7 +184,7 @@ The delivery system enforces per-agent permissions: - 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. diff --git a/features/customization.mdx b/features/customization.mdx index 9940fa8..cf403e0 100644 --- a/features/customization.mdx +++ b/features/customization.mdx @@ -173,7 +173,7 @@ Agents only see what you mount. The allowlist lives at `~/.config/nanoclaw/mount } ``` -Per-agent-group mount requests live in `groups//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//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. ## The `/customize` skill