diff --git a/advanced/container-runtime.mdx b/advanced/container-runtime.mdx index 045e018..a9e1c88 100644 --- a/advanced/container-runtime.mdx +++ b/advanced/container-runtime.mdx @@ -77,12 +77,14 @@ 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: +Each agent group has a row in the `container_configs` table holding its custom apt and npm package lists. 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 +Manage the package lists with `ncl groups config add-package` / `remove-package`. Restart the running container with `ncl groups restart --id --rebuild` to apply changes — package CLI operations no longer auto-kill containers. + ## Two-database IO model In v2, all communication between host and container uses two SQLite databases per session. There is no stdin/stdout piping, no IPC files, and no output markers. @@ -121,7 +123,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 row from the `container_configs` table, materializes a snapshot to `container.json` for the container to read, and resolves provider contributions. @@ -151,7 +153,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 snapshot of the `container_configs` row | | 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 | @@ -170,8 +172,23 @@ Containers have two timeout/detection mechanisms: ### Container shutdown - `killContainer(sessionId, reason)` stops the container via `docker stop`, falls back to SIGKILL +- An optional `onExit` callback fires after the container process exits — used by restart flows to guarantee the old container is fully gone before respawn - On close/error, the session is marked stopped and typing indicators are cleared +### Explicit restart and on-wake messages + +Container restart is always explicit — config CLI operations do not auto-kill containers. Use the admin CLI: + +```bash +ncl groups restart --id [--rebuild] [--message "context for the agent"] +``` + +- `--message` writes an on-wake message that is only delivered to the fresh container's first poll iteration (via the `on_wake` flag on `messages_in`) +- `--rebuild` rebuilds the per-agent-group image first +- From inside a container, `--id` is auto-filled and only the calling session is restarted + +The on-wake mechanism eliminates the race where a dying container — still in its SIGTERM grace period — could steal the message intended for its replacement. Self-mod approval handlers (`install_packages`, `add_mcp_server`) use the same flow. + ## Credential injection The OneCLI SDK's `applyContainerConfig()` configures each container's network to route through the vault: diff --git a/api/configuration.mdx b/api/configuration.mdx index 782d8de..af47d66 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 managed through environment variables, the `.env` file, and the `src/config.ts` module. In v2, per-agent-group runtime config (provider, model, packages, MCP servers, mounts, `cli_scope`) lives in the `container_configs` table in the central DB, managed via the `ncl` admin CLI. Existing `groups//container.json` files from earlier installs are backfilled into the DB automatically on startup; the file is still materialized at spawn time so the container runner can read it. ## Environment variables diff --git a/api/group-management.mdx b/api/group-management.mdx index 0bfaa96..e3173da 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 configuration lives in the `container_configs` table in the central DB (provider, model, packages, MCP servers, mounts, `cli_scope`); a snapshot is materialized to `container.json` 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 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 | @@ -160,6 +161,21 @@ Each session has two databases in `data/v2-sessions/{agent_group_id}/{session_id | `session_state` | Persistent key/value store | | `container_state` | Tool-in-flight tracking | +## Admin CLI (`ncl groups`) + +The `ncl` admin CLI exposes group and container-config management. Write operations require approval from inside containers; on the host they run directly. + +| Verb | Purpose | +|------|---------| +| `list`, `get`, `create`, `update`, `delete` | Standard CRUD over `agent_groups` | +| `restart` | Kill containers for a group; pass `--rebuild` to rebuild the image and `--message ` to deliver an on-wake message to the fresh container | +| `config get` | Show the container config row | +| `config update` | Update scalar fields (`--provider`, `--model`, `--effort`, `--image-tag`, `--assistant-name`, `--max-messages-per-prompt`, `--cli-scope`) | +| `config add-mcp-server` / `config remove-mcp-server` | Manage the MCP servers attached to a group | +| `config add-package` / `config remove-package` | Manage apt and npm package lists | + +Config CLI operations no longer auto-kill containers — call `ncl groups restart` explicitly to apply changes. Group-scoped agents (where `cli_scope = 'group'`) cannot change `cli_scope` on themselves or any other group. + ## Channel approval flow When a message arrives on an unwired channel: diff --git a/changelog/docs-updates.mdx b/changelog/docs-updates.mdx index 3da553f..800e9fd 100644 --- a/changelog/docs-updates.mdx +++ b/changelog/docs-updates.mdx @@ -5,6 +5,19 @@ icon: "file-pen" rss: true --- + + Documented three v2.0.46–v2.0.48 features that change how per-agent-group config is stored and how containers are restarted. + + ## Updated + - **`concepts/containers`**: container config now lives in `container_configs` (DB), not `container.json`; the file is a spawn-time snapshot. Added explicit-restart and on-wake message section. MCP server and package management commands now reference `ncl groups config`. + - **`concepts/groups`**: agent group definition references the `container_configs` row. New "CLI scope" section covering `disabled` / `group` / `global` and escalation blocks. + - **`api/configuration`**: per-agent-group runtime config moved to the central DB; `container.json` is materialized on spawn. + - **`api/group-management`**: added `container_configs` table to schema. New "Admin CLI (`ncl groups`)" section listing all verbs and behavior. + - **`advanced/container-runtime`**: spawn flow reads `container_configs`; `killContainer` exposes an `onExit` callback. New "Explicit restart and on-wake messages" section. + - **`features/customization`**: mount config moved to the `additional_mounts` column of `container_configs`. + - **`changelog/index`**: added v2.0.45 (admin CLI, v1→v2 migration) and v2.0.48 (DB-backed config, on-wake, CLI scope) release entries. + + Phase A of the v2 documentation sprint — bringing the pages every new user lands on into alignment with the v2 rewrite. All claims verified directly against upstream source (`src/db/schema.ts`, `src/types.ts`, `src/config.ts`, `container/Dockerfile`, `src/delivery.ts`) rather than upstream `docs/` (which includes a stale `architecture.md` draft and a `db-session.md` that omits the `container_state` table). diff --git a/changelog/index.mdx b/changelog/index.mdx index 0608705..0d62e34 100644 --- a/changelog/index.mdx +++ b/changelog/index.mdx @@ -6,6 +6,17 @@ rss: true tag: "UPDATED" --- + + - Container config moved to the central DB — per-agent-group runtime config (provider, model, packages, MCP servers, mounts, skills) now lives in the `container_configs` table instead of `groups//container.json`. Existing filesystem configs are backfilled automatically on startup. Managed via `ncl groups config get/update` and `config add-mcp-server/remove-mcp-server/add-package/remove-package`. + - Explicit restart with on-wake messages — config CLI operations no longer auto-kill containers. New `ncl groups restart` command with `--rebuild` and `--message` flags. On-wake messages (`on_wake` column on `messages_in`) are only picked up by a fresh container's first poll, preventing dying containers from stealing them during the SIGTERM grace period. Self-mod approval handlers (`install_packages`, `add_mcp_server`) use the same race-free mechanism. + - Per-group CLI scope — new `cli_scope` setting on container config (`disabled` / `group` / `global`, default `group`). Controls what the agent can access via `ncl` from inside the container. `disabled` excludes CLI instructions from `CLAUDE.md` and blocks all requests. `group` (default) restricts to own-group resources with auto-filled args. `global` gives unrestricted access (set automatically for owner agent groups). Includes post-handler result filtering to prevent cross-group data leaks and blocks `cli_scope` escalation from group-scoped agents. + + + + - Admin CLI (`ncl`) — new `ncl` command for querying and modifying the central DB: agent groups, messaging groups, wirings, users, roles, members, destinations, sessions, approvals, and dropped messages. Host-side transport via Unix socket; container-side transport via session DB. Write operations from inside containers go through the approval flow. `list` supports column filtering and `--limit`. Run `ncl help` for usage. + - v1 → v2 migration — run `bash migrate-v2.sh` from the v2 checkout. Finds your v1 install, merges `.env`, seeds the v2 DB from `registered_groups`, copies group folders, copies session data with conversation continuity, ports scheduled tasks, interactively selects and installs channels, copies container skills, builds the agent container, and offers a service switchover. Hands off to Claude (`/migrate-from-v1`) for owner seeding, access policy, and CLAUDE.md cleanup. + + - Ground-up architectural rewrite with new entity model (users, roles, messaging groups, agent groups, wirings) - Two-database session model — `inbound.db` (host writes) and `outbound.db` (container writes) eliminate cross-mount SQLite contention diff --git a/concepts/containers.mdx b/concepts/containers.mdx index c328ee0..4f27ccb 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) | +| Container config | `/workspace/agent/container.json` | Read-only | Materialized snapshot of the DB row 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,11 +82,11 @@ 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 agent group's container config (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. - + +Per-agent-group container config (provider, model, packages, MCP servers, mounts, skills, `cli_scope`) lives in the `container_configs` table in the central DB. The `container.json` mount is materialized from the DB row at spawn time so the container runner sees a stable snapshot. Existing `groups//container.json` files are backfilled into the DB automatically on startup. + ### Mount security @@ -143,18 +143,38 @@ Container spawning is deduplicated — concurrent wake calls for the same sessio - **Stale detection**: host sweep detects containers with old heartbeats or stuck processing_ack - **Fallback**: SIGKILL if graceful stop fails +### Explicit restart and on-wake messages + +Use `ncl groups restart --id [--rebuild] [--message ]` to restart all running containers in an agent group. Config CLI operations no longer auto-kill containers — restart is always explicit. + +- `--rebuild` rebuilds the per-agent-group image first +- `--message` writes an on-wake message that the fresh container picks up on its first poll +- Without `--message`, containers come back on the next user message +- From inside a container, `--id` is auto-filled and only the calling session is restarted + +On-wake messages are race-free: the `on_wake` flag on `messages_in` ensures the message is only delivered to a fresh container's first poll iteration. A dying container in its SIGTERM grace period can never steal it. The same mechanism is used by the self-mod approval handlers (`install_packages`, `add_mcp_server`). + 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 -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 apt and npm packages on their container config row. The host builds a derived Docker image with the additional 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 +Manage packages via the admin CLI: + +```bash +ncl groups config add-package --id --type apt --name jq +ncl groups config remove-package --id --type npm --name some-pkg +``` + +Package CLI operations no longer auto-kill containers. Use `ncl groups restart --id --rebuild` to apply package changes to running containers. + ## Timeouts ### Container timeout @@ -187,7 +207,12 @@ 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 are stored on the agent group's container config row and managed via the admin CLI: + +```bash +ncl groups config add-mcp-server --id --name my-server --command /path/to/server +ncl groups config remove-mcp-server --id --name my-server +``` ### Global memory injection diff --git a/concepts/groups.mdx b/concepts/groups.mdx index 381153b..016df6d 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`) - The unit of credential scoping (each gets its own OneCLI agent) ### Messaging groups @@ -137,17 +137,13 @@ The global memory directory (`groups/global/`) provides shared context: ## Additional mounts -Agent groups can have extra directories mounted via `container.json`: +Agent groups can request extra host directories on their container config row (`additional_mounts`). Each entry has the shape: ```json { - "additionalMounts": [ - { - "hostPath": "/Users/you/projects/website", - "containerPath": "website", - "readonly": true - } - ] + "hostPath": "/Users/you/projects/website", + "containerPath": "website", + "readonly": true } ``` @@ -164,6 +160,24 @@ Validated mounts appear at `/workspace/extra/{name}` inside the container and ar Additional mounts bypass group isolation. Only mount directories that are safe for the agent to access. +## CLI scope + +Each agent group has a `cli_scope` setting on its container config that controls what the agent can access via `ncl` from inside the container: + +| Value | Behavior | +|-------|----------| +| `disabled` | Agent never learns about `ncl` — CLI instructions are excluded from `CLAUDE.md` and the host rejects every request | +| `group` (default) | Agent can only access its own group's resources (`groups`, `sessions`, `destinations`, `members`). `--id` and group-scoped args are auto-filled. Cross-group access is rejected. | +| `global` | Unrestricted access. Set automatically for owner agent groups via `init-first-agent`. | + +Group-scoped agents cannot escalate their own scope — `cli_scope` updates are blocked on requests originating inside a group-scoped container. Post-handler result filtering also prevents cross-group data from leaking through `list`/`get` responses. + +Update via: + +```bash +ncl groups config update --id --cli-scope global +``` + ## Concurrency Container concurrency is managed globally: diff --git a/features/customization.mdx b/features/customization.mdx index 9940fa8..2fd2cd1 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 `container_configs` table (`additional_mounts` column) and are managed via `ncl groups config update`. The host validates each request against the allowlist before mounting. See [Security model](/advanced/security-model) for the full picture. ## The `/customize` skill