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. Sincecontainer.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_scopeis enforced host-side and read fresh from the database on every CLI request — it takes effect immediately, no restart.timezoneis 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 ownTZ(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):
sessions.agent_provider(per-session override)container_configs.provider'claude'
model and effort
Passed throughcontainer.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’sname 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-containerncl 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) — onlygroups,sessions,destinations,members, andtasksresources pluswirings get/updatefor the current conversation’s wiring, auto-scoped to the agent’s own group; the agent cannot changecli_scopeitselfglobal— full resource access; mutations still require approval
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 aheaders 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.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:
container only, and validateSpec rejects a spec asking for anything outside that list before any container is created:
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.