Skip to main content
Each agent group has one row in the container_configs table in the central SQLite database — the source of truth for how that group’s containers spawn. You change it with the ncl groups config subcommands; the row is created automatically with defaults when the group is created. At every spawn, NanoClaw materializes the row to groups/<folder>/container.json and mounts it read-only at /workspace/agent/container.json inside the container. It’s a generated file — any manual edits are overwritten on the next spawn, and the agent can read but not modify it.

When changes take effect

Config edits write to the database only — a running container keeps its old config until it exits. Since container.json is re-materialized at every spawn, changes apply to the next container automatically; run ncl groups restart --id <group-id> to force it immediately. The following fields have additional behavior:
  • Packages are baked into the group’s image, not installed at spawn — they need ncl groups restart --rebuild.
  • cli_scope is enforced host-side and read fresh from the database on every CLI request — it takes effect immediately, no restart.
  • timezone is partly immediate: scheduled tasks read it fresh from the database at trigger time, so a change applies to the next run with no restart. The container’s own TZ (message timestamps, anything the agent shells out for) is only set at spawn, so that side still needs a restart.

Fields

provider

Which agent CLI runs in the container. Resolution order at spawn (resolveProviderName in container-runner.ts):
  1. sessions.agent_provider (per-session override)
  2. container_configs.provider
  3. 'claude'
The result is lowercased and looked up in the provider registry, which may contribute extra mounts and env vars (e.g. OpenCode’s XDG dirs).

model and effort

Passed through container.json to the agent runner, which hands them to the provider unchanged. For Claude, model is an alias (sonnet, opus, haiku) or a full model ID, and effort is one of low, medium, high, xhigh, max. An unset (NULL) group model uses NANOCLAW_DEFAULT_MODEL, then the provider default if that setting is empty. A group-specific model takes precedence. effort still falls back to the provider default. NANOCLAW_FAST_MODE=1 or true enables the install-wide Claude fast serving tier, passed as settings.fastMode to its SDK. It is off by default and is not a container_configs column or per-group CLI flag. These environment settings are read when the host starts; restart the host and the affected sessions after changing them. A host restart alone can adopt existing containers, which retain their previous settings.

image_tag

The image the container runs: image_tag if set, otherwise the base image (CONTAINER_IMAGE). You normally don’t set this by hand — see per-group images below.

assistant_name

The agent’s name, injected into the system prompt (“Your name is X…”). Falls back to the agent group’s name when unset (configFromDb applies the fallback at materialization).

max_messages_per_prompt

Cap on how many pending messages are batched into a single prompt. The runner defaults to 10 when unset.

cli_scope

Host-side enforcement of what the agent can do through the in-container ncl CLI. One of:
  • disabled — all CLI requests rejected; the CLI instructions fragment is also dropped from the composed CLAUDE.md (that part applies at next spawn)
  • group (default) — only groups, sessions, destinations, members, and tasks resources plus wirings get/update for the current conversation’s wiring, auto-scoped to the agent’s own group; the agent cannot change cli_scope itself
  • global — full resource access; mutations still require approval
This is the only field not written to container.json — the host checks the database on every request. See ncl CLI reference for the full enforcement rules.

skills

Either the string "all" or an array of skill names. Before mounting, the host syncs symlinks in the group’s .claude-shared/skills/ directory to match: "all" re-reads container/skills/ on every spawn (newly added skills appear automatically), an array pins the exact set. Each symlink targets /app/skills/<name> — valid inside the container, dangling on the host. There is no ncl groups config subcommand for this field as of v2.1.38; update the JSON column directly in the database.

mcp_servers

Remote HTTP servers can include a headers object. Plugin-owned servers cannot be replaced or removed through the CLI or the approval-based self-modification tool; edit the plugin and restamp instead. External MCP servers merged with the built-in nanoclaw server at runner startup. Each entry is either a stdio server or a remote Streamable HTTP server:
args and env are optional and valid only on the stdio form (type defaults to stdio). Server names must be 1-64 chars of letters, digits, _, -. An http entry’s url must be HTTPS — plain HTTP passes only for localhost, 127.0.0.1, [::1], and host.docker.internal — and URLs carrying credentials, fragments, or secret-looking query parameters are rejected. instructions, if present, is inlined into the group’s composed CLAUDE.md at spawn as a # MCP Server: <name> section — but neither ncl groups config add-mcp-server nor the agent’s add_mcp_server tool accepts it as of v2.1.38; to set it, update the JSON column directly in the database. Manage the rest with ncl groups config add-mcp-server / remove-mcp-server.

packages_apt and packages_npm

Extra packages for the group’s container. They are not installed at spawn — buildAgentGroupImage bakes them into a per-group image (see below). Manage with ncl groups config add-package / remove-package, then ncl groups restart --rebuild --id <group-id>.

additional_mounts

Extra host directories mounted into the container:
containerPath is relative — the mount lands at /workspace/extra/<containerPath> (defaults to the host path’s basename). Every entry is validated at spawn against the allowlist at ~/.config/nanoclaw/mount-allowlist.json: no allowlist file means all additional mounts are blocked, paths matching blocked patterns (.ssh, .aws, .env, …) are always rejected, and read-write requires both "readonly": false and an allowed root with allowReadWrite: true — otherwise the mount is forced read-only. Rejected mounts are logged and skipped, not fatal. Full allowlist setup in Hardening.

timezone

An IANA timezone id (e.g. Europe/Lisbon) that overrides the install-global timezone for this group alone. Unset, or an invalid id, falls back to the global default rather than silently running in UTC. Set it with ncl groups create --timezone <id> or ncl groups config update --timezone <id>; pass --timezone "" on config update to clear it back to the install default. It reaches two places: the container’s TZ env var (spawn-time only), and scheduled-task recurrence, which resolves the group’s timezone fresh from the database at every trigger check — so a change to this field changes when a group’s schedules fire immediately, ahead of the container-level effects in When changes take effect above.

runtime_tier

The session isolation tier for this group’s containers. Two values are valid: container and vm. An absent value falls back to container.
Trunk carries no runtime_tier column. No migration adds it, ncl cannot write it, ncl groups config get does not display it, and no environment variable sets it. On a stock install every group runs at container. The field exists for a deployment that registers a driver able to run a second tier. That deployment adds the column itself and writes the value straight to the database.
The tier travels from one place only. configFromDb reads container_configs.runtime_tier, and spawn composition resolves containerConfig.runtimeTier ?? 'container' into the session spec. There is no session-level override, unlike provider. NanoClaw refuses to compose the group if the stored value is neither container nor vm:
The refusal is deliberate. Falling back to the default would quietly give the group a weaker boundary than the stored value asked for, so the group stays down until you fix the row. Every driver declares the tiers it can realize. The shipped Docker driver declares container only, and validateSpec rejects a spec asking for anything outside that list before any container is created:
That failure is not retryable, and it is a refusal rather than a fallback. The Docker driver maps no flag from the tier, so container is the only tier trunk can run. A group with no stored value writes nothing into the generated container.json. Where the column exists, the file carries the value as runtimeTier, in camel case. That copy is informational: the database is still the source, and editing the file changes nothing. Driver selection itself is separate from the tier. NANOCLAW_RUNTIME_DRIVER picks the driver and defaults to docker.

Per-group images and packages

When a group has packages configured, buildAgentGroupImage generates a Dockerfile FROM the base image, installs the apt packages (apt-get install) and npm packages (pnpm install -g, with each npm package allowlisted for build scripts so postinstall hooks like Playwright’s browser download actually run), builds it as CONTAINER_IMAGE_BASE:<agent-group-id>, and stores that tag in image_tag. From then on the group spawns from its custom image. The build is triggered by ncl groups restart --rebuild or by the agent’s own install_packages tool. It throws if both package lists are empty.

Reading and editing

config get is open access; all mutations require approval. Full flag listing in the ncl CLI reference.
Last modified on September 4, 2026