install_packages and add_mcp_server, and both are always approval-gated: the agent submits a request, an admin gets a card, and nothing changes until someone taps Approve. What an agent can change freely is its own memory and standing instructions.
What an agent can change
Free edits: the agent’s own memory
The group folder is mounted read-write at/workspace/agent/, so the agent can edit its own durable state without asking anyone: the memory/ tree (durable facts and accumulated memory, scaffolded idempotently at boot for every provider) and instructions.prepend.md (its standing instructions and persona). This is where “remember that I prefer short answers” lands — in memory/. The self-customize container skill teaches exactly this: memory, standing instructions, and workspace files are direct edits, everything else has a workflow.
The boundary is enforced by nested read-only mounts on top of the read-write group dir:
- the composed
CLAUDE.mdis read-only and regenerated from scratch every spawn — edits would be lost anyway, so writes are blocked and the header points the agent at the writable surfaces instead:instructions.prepend.mdfor standing instructions,memory/for memory container.json(the materialized container config) is read-only — config changes only land through the approval paths below- the agent-runner source at
/app/srcis read-only — code changes go through a builder agent, not in-place edits
install_packages: apt and npm packages
The agent calls the install_packages MCP tool with apt and/or npm package lists and an optional reason. Constraints, validated in the container and re-validated on the host (the payload travels verbatim to a shell exec, so both layers matter): at least one package, max 20 per request, strict name patterns — no version specs, flags, or shell characters.
The request is fire-and-forget. On approval, the host does the whole follow-up in one step:
- Appends the packages to the group’s
packages_apt/packages_npmconfig (deduplicated) - Rebuilds the per-agent image
- Kills the container and queues an on-wake message telling the fresh agent to verify the packages and report back to the user
pnpm install in the per-session workspace, which needs no approval but doesn’t persist into the image or other sessions. The self-customize skill recommends exactly that for one-off tasks, and prototyping in the workspace before promoting a dependency to a container-level install.
add_mcp_server: new tools
The agent calls add_mcp_server with a name and command (plus optional args and env) for an MCP server it already knows how to invoke. On approval, the host adds the server to the group’s mcp_servers config and restarts the container — no image rebuild, since bun runs TypeScript directly and the new server is wired on the next start.
Two things this path can’t do: it doesn’t accept the instructions field that mcp_servers entries support, and it doesn’t handle credentials — agents are instructed to use the "onecli-managed" placeholder for any credential env vars and let the OneCLI Agent Vault inject real values in flight, never to ask you for keys.
The approval flow, from your seat
Both operations use the same approvals plumbing as credential requests. You get a DM card — “Install Packages Request” or “Add MCP Request” — naming the agent, the package list or server command, and the agent’s stated reason, with Approve, Reject, and Reject with reason… buttons.- Who gets it: admins of the requesting agent group first, then global admins, then owners — the first one with a reachable DM, preferring approvers on the same channel type as the originating session, then falling back to list order on any channel. No eligible approver means the request fails immediately and the agent is told why.
- On approve: the host executes the full change (config update, rebuild if needed, restart) and the agent gets a system message prompting it to verify and report.
- On reject: a plain Reject tells the agent “Your
install_packagesrequest was rejected by admin.” Reject with reason… instead prompts you for a one-line note in your DM and relays it —Your install_packages request was rejected by admin: "<reason>"— so the agent learns why. Don’t answer within ~5 minutes and it finalizes as a plain reject.
ncl approvals list --status pending; decisions happen on the card, not in the CLI.
Self-mod is an optional module. Without it installed, the agent’s tools still submit requests, but the host drops them — no card is ever delivered and nothing changes.
Reviewing and reverting changes
Everything an agent changed through approval lands in the group’s container config, so review is one command:groups/<folder>/ on the host (the memory/ tree and instructions.prepend.md), and the groups directory is not a git repository. To review, read the files; to revert, edit them (or restore from your own backups).
Where the guardrails sit
An agent asking to expand its own capabilities is exactly what a prompt-injected agent would ask for — that’s why both operations are approval-gated and package names are sanitized at two layers. The security model covers self-modification in the context of the other approval gates.Don’t confuse agent self-modification with the operator-side
/customize skill, which you run in Claude Code against your NanoClaw checkout to change channels, wirings, and core behavior. That’s covered in Customize NanoClaw.